Cpp_函数调用过程
函数调用过程
- 参数入栈
- 函数栈帧开辟
- 返回值返回
- 函数栈退出
代码
1 | //4字节入栈 |
1 | //8字节入栈 |
1 | //12字节入栈 |
寄存器
可以理解为CPU中的变量
如何去标志一个栈?——栈底和栈顶指针。
esp:栈顶寄存器。
ebp:栈底寄存器。
函数调用过程
-
参数入栈(C语言)
-
4字节参数(dword)入栈
- 顺序:从右向左
- 方式:使用寄存器push带入
-
8字节参数入栈
- 顺序:从右向左
- 方式:使用寄存器分两次push带入
-
(>8字节)12字节参数入栈
- 顺序:从右向左
- 方式:变了,栈顶先上移,即开辟足够的空间,之后利用寄存器将数据分批次复制进去。
-
C++中,只要是自定义类型,无论多大字节,都采用先esp上移开辟空间,之后分次赋值的方式(即方式3),即>8字节的情况。
-
-
函数栈帧开辟
其中,1、2步是为了保存现场。3、4步是开辟新的栈帧。- 函数入参的动作完成后,汇编代码执行call(_fun)语句,esp向上移4位,将调用方函数原来的下一行指令地址存入栈。因为main中调用完成外部函数后,还要回到以前的位置。
- 将调用方函数的栈底寄存器(ebp)入栈
- 让
ebp=esp
(让ebp上移到esp的位置) - 让
esp=esp-0**h
(上移若干空间,开辟此函数的空间) - 将某些寄存器(线程保护)入栈
- 将新开辟的栈帧空间中全部赋为
0xcccccccc
- 函数入参的动作完成后,汇编代码执行call(_fun)语句,esp向上移4位,将调用方函数原来的下一行指令地址存入栈。因为main中调用完成外部函数后,还要回到以前的位置。
-
函数返回值
-
4字节返回值:将返回值先存到eax再赋给接收变量
-
8字节返回值:将返回值分开放到两个寄存器,再赋给接收变量
-
(>8字节)12字节返回值:
- 在函数参数入栈之后,入栈一个调用方(如main)栈帧上的地址(靠近栈顶位置)
- 在返回值返回的时候,将返回数据分次写入到之前入栈的调用方地址上。
- 返回之后,将从该地址(调用方(如main)栈帧上的地址)将数据分次取出(一次4字节)到接收变量中。
-
C++中,自定义类型都按照入栈一个调用方地址的方式
-
-
函数栈退出
- 进行当前函数栈帧的校验
- 将线程保护的寄存器出栈
- 让
esp=ebp
(回缩栈帧) pop
操作(即将pop的位置是esp指向的位置)。
pop ebp
,意为:ebp=pop
,esp
指向的是原来main
的ebp
地址,赋给ebp
,同时esp
下移4字节。则ebp
回指到原来的位置。形成现场恢复。
- 将下一行指令的地址还原(
ret
),实际上,也是一个pop
,返回保留的地址值,esp
下移4字节。 - esp再次下移若干,清除参数和之前入栈的调用方地址。下移之后,则esp和ebp回到了main函数调用外部函数之前的原始位置。函数调用结束。
说明
当前演示的函数调用规则是依赖于C语言默认的调用约定__cdecl
,其他的还有__stdcall
/__fastcall
。三种差异并不大,只是负责的事情不同,清除参数是调用方执行的,stdcall
是被调用方执行的。
1 | struct Node __cdecl fun(int a, int b) |