函数调用时栈空间的变化
-
初始栈空间
-
传参,通过push指令先将参数压入堆栈(c语言:从右向左压入)
-
call指令函数入口地址(跳转到函数,并将下一条语句地址push到堆栈中)
-
push ebp(保存栈底指针,用于后面恢复栈底)
-
mov ebp, esp(提升栈底指针,准备给函数分配栈空间)
-
sub esp, xxx(提升栈顶指针,开辟专属该函数的栈空间,共该函数局部变量使用)
-
push 寄存器(保存进入该函数前的寄存器的值,保证函数调用完后可以恢复原始值)
-
执行函数代码
-
pop 寄存器
-
mov esp, ebp(降低栈顶指针,释放专属该函数的栈空间)
-
pop ebp(降低栈底指针,恢复成原始值)
-
retn(pop eip,此时栈顶指针指向的位置刚好保存着进入函数之前的下一条语句的地址)
-
add esp, xxx(由于调用函数前push参数导致栈顶指针提升,因此调用完要释放掉参数栈空间)
问题:
- 为什么要进行堆栈平衡?
因为Windows操作系统应用层堆栈大小默认是1M,每次调用函数都会开辟一段堆栈空间,用完如果不平衡,调用几次函数就凉凉了
- 函数的返回值放哪里了?
大部分情况返回值会放到eax中,但不是绝对的。
- 传参只能通过push到堆栈的方式吗?
也可以通过寄存器传参。
函数调用约定:
- __cdecl: C/C++里中默认调用方式
特点1:push参数,顺序从右往左。
特点2:外部平衡堆栈。(即在retn之后(函数外部)释放参数栈空间)
- __stdcall:windows API函数的调用方式 用了WINAPI的宏进行代替
特点1:push参数,顺序从右往左。
特点2:内部平衡堆栈。(即在retn之前(函数内部)释放参数栈空间)
- __fastcall:快速调用方式 这种方式选择将参数优先从寄存器传入
特点1:寄存器传参,edx,ecx (如果参数多于2个,则用push),顺序从右向左
特点2:内部平衡堆栈。(即在retn之前(函数内部)释放参数栈空间)