函数堆栈调用和函数值的返回方式以及函数的调用约定
函数堆栈调用:
我们先用一段简单的程序,通过反汇编的方式来分析一下函数的堆栈调用
反汇编代码:
我们现在来分析一下:
ebp:栈底指针 esp:栈顶指针 pc:下一行指令寄存器
从main函数开始进入:
11: int a = 10;
0010188E mov dword ptr [a],0Ah //开辟空间[a] 赋值为10
12: int b = 20;
00101895 mov dword ptr [b],14h //开辟空间[b] 赋值为20
13: int rt = Sum(a, b);
0010189C mov eax,dword ptr [b] //取空间[b]中的值,放入寄存器eax中
0010189F push eax //将寄存器eax中值压栈
001018A0 mov ecx,dword ptr [a] //取空间[b]中的值,放入寄存器eax中
0010189F push eax //将寄存器eax中值压栈
001018A3 push ecx
001018A4 call Sum (01011F4h) //call指令:1.调用Sum函数,并将下一行指令地址压栈 2.jump到被调用方函数
001018A9 add esp,8
001018AC mov dword ptr [rt],eax
进入到Sum函数中:
3: int Sum(int a, int b)
4: {
00101830 push ebp //将ebp压栈,压入调用方的栈底指针的值
00101831 mov ebp,esp //移动ebp,将其移动到esp指针的地方
00101833 sub esp,0CCh //累减器
00101839 push ebx
0010183A push esi
0010183B push edi
0010183C lea edi,[ebp-0CCh] //移地址符
00101842 mov ecx,33h
00101847 mov eax,0CCCCCCCCh
0010184C rep stos dword ptr es:[edi]
5: int tmp = a + b;
0010184E mov eax,dword ptr [a] //取空间[a]中的值,放入寄存器eax中
00101851 add eax,dword ptr [b] //取空间[b]中的值,并将其值加入到寄存器eax中
00101854 mov dword ptr [tmp],eax //取寄存器eax中的值,放入空间[tmp]中
6: return tmp;
00101857 mov eax,dword ptr [tmp] //取空间[tmp]中的值,并将其值放入到寄存器eax中
7: }
0010185A pop edi
0010185B pop esi
0010185C pop ebx
0010185D mov esp,ebp
0010185F pop ebp
00101860 ret //pc(下一行指针寄存器),ret => pop pc => pc = pop();
压栈:
调用函数:
- 压入形参变量的地址和值
- 压入函数调用返回后要执行的指令的地址
被调用函数:
- 压入调用函数的栈底指针,把栈底指针寄存器指向被调用函数的栈底
- 开辟被调用函数的栈帧大小,并初始化为0xCC CC CC CC
清栈:
调用函数:
- 清理形参变量的内存
被调用函数:
- 清理被调用函数开辟的栈帧大小
- 回退栈底指针到调用函数的栈底
- 把回退到调用函数下一行要执行的指令传入到pc寄存器
函数值的返回方式:
若返回值的大小为x
0<= x <= 4字节:则通过一个寄存器带回 4< x <=8字节:则通过两个寄存器带回
8< x字节:则通过临时量带回(临时量内存开辟在调用方内存块内,通过拷贝带回,临时量紧挨着实参)
函数的调用约定:
_cdecl C标准调用约定 ------------
_stdcall Windows标准调用约定 |------ >C,C++内约定,自右向左入栈
_fastcall 快速调用约定 ------------
_thiscall 类成员方法的调用约定
前三种调用约定的区别:
1:函数符号的生成
int Sum(int a, int b); //int __cdecl Sum(int,int)" (?Sum@@YAHHH@Z)
int _stdcall Sum(int a, int b); //int __stdcall Sum(int,int)" (?Sum@@YGHHH@Z)
int _fastcall Sum(int a, int b); //int __fastcall Sum(int,int)" (?Sum@@YIHHH@Z)
2:实参的入栈顺序
_cdecl调用约定:参数从右向左压入堆栈
_stdcall调用约定:参数从右向左压入堆栈
_fastcall调用约定:第一个和第二个参数通过寄存器ecx和edx传递,其他参数从右向左压入堆栈
3:形参的开辟和清理方式
_cdecl 调用方开辟,调用方清除(正是这种特性,C语言允许函数的参数个数不固定)
_stdcall 调用方开辟,被调用方清除(用寄存器保存值,若个数 >= 3个,则从第三个开始的形参开辟内存保存,被调用方清理)
_fastcall 内置类型 <= 4,前两个形参不开辟内存,通过寄存器带入, 其他 调用开辟,被调用方清理