在计算机科学中, 调用约定(Calling Conventions)是一种定义子过程从调用处接受参数以及返回结果的方法的约定。
函数调用时, 调用者依次把参数压栈, 然后调用函数, 函数被调用以后, 在堆栈中取得数据, 并进行计算. 函数计算结束以后, 或者调用者、或者函数本身修改堆栈, 使堆栈恢复原装. 在参数传递中, 有几个很重要的问题必须得到明确说明:
- 当参数个数多于一个时, 按照什么顺序把参数压入堆栈
- 函数调用后, 由谁来把堆栈恢复原装
- 函数的返回值放在什么地方
CDECL
1 2 | int function(int a, int b) //不加修饰默认就是 C 调用约定 int __cdecl function(int a, int b) //明确指定 C 调用约定 |
cdecl 是 C Declaration 的缩写,表示 C 语言默认的函数调用方法:
- 参数从右向左压入堆栈
- 函数本身不清理堆栈,调用者负责清理堆栈,因此 C 调用约定允许函数的参数的个数是不固定的
函数调用 function(1, 2)
调用处翻译成汇编语言将变成:
1 2 3 4 | push 2; push 1; call function; add esp, 8; //注意, 这里调用者再恢复堆栈 |
被调用函数:
1 2 3 4 5 6 7 | push ebp; 保存 ebp, 该寄存器将用来保存堆栈的栈顶指针, 可以在函数退出时恢复 mov ebp, esp; 保存栈顶指针 mov eax, [ebp + 8H]; 堆栈 ebp 指向位置之前依次保存有 ebp, cs:eip, a, b, ebp + 8 指向 a add eax, [ebp + 0CH]; 堆栈中 ebp + 12 处保存了 b mov esp, ebp; 恢复 esp pop ebp; ret; |
STDCALL
1 | int __stdcall function(int a, int b) |
stdcall 是 Standard Call 的缩写,是 C++ 的标准调用方式:
- 参数从右向左压入堆栈
- 函数自身修改堆栈
函数调用 function(1, 2)
调用处翻译成汇编语言将变成:
1 2 3 | push 2; 第二个参数入栈 push 1; 第一个参数入栈 call function; 调用函数, 注意此时自动把 cs:eip 入栈 |
而对于函数自身, 则可以翻译为:
1 2 3 4 5 6 7 | push ebp; 保存 ebp 寄存器, 该寄存器将用来保存堆栈的栈顶指针, 可以在函数退出时恢复 mov ebp, esp; 保存栈顶指针 mov eax, [ebp + 8H]; 堆栈中 ebp 指向位置之前依次保存有 ebp, cs:eip, a, b, ebp + 8 指向 a add eax, [ebp + 0CH]; 堆栈中 ebp + 12 处保存了 b mov esp, ebp; 恢复 esp pop ebp; ret 8; |
FASTCALL
1 | int __fastcall function(int a, int b) |
fastcall 是编译器指定的快速调用方式:
- 函数的第一个和第二个 DWORD 参数(或者尺寸更小的)通过 ecx 和 edx 传递, 其他参数通过从右向左的顺序压栈
- 被调用函数清理堆栈
总结
简单举例
从图中可以了解到:
- cdecl 平衡堆栈是在 call 结束回来后
add esp,0x8
(参数占用空间) - stdcall 平衡堆栈是在 call 内部返回时
retn 0x8
参考
- 本文作者: Kasen
- 本文链接: 关于调用约定 cdecl、stdcall 和 fastcall 的区别 | 拾遗记
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!