函数调用:__cdecl、__stdcall和__fastcall
知识的搬运工
引用:
https://blog.csdn.net/JHXXH/article/details/146565235 笼统的
https://www.cnblogs.com/a3192048/p/12241316.html 很细致,细致得有点看不下去
函数调用约定
在C/C++开发中,**函数调用约定(Calling Convention)**决定了函数参数如何传递、栈空间由谁清理、函数名修饰规则等底层细节。理解不同的调用约定对调试、逆向工程和跨语言调用至关重要。
什么是函数调用约定?
函数调用约定定义了以下内容:
- 参数压栈顺序:从左到右还是从右到左压栈。
- 栈的清理责任:由调用者(Caller)还是被调用函数(Callee)清理栈(把参数弹出栈)。
- 寄存器使用规则:哪些寄存器用于传递参数或保存中间值。
- 函数修饰名:编译器如何生成函数在二进制文件中的符号名。
cdecl(C Declaration)
C/C++ 的默认调用约定,广泛用于跨平台代码中。
特点:
- 参数从右到左依次压栈。
- 调用者负责清理栈。每次调用函数时,调用者必须在函数返回后手动调整栈指针。
- 可变参数支持。因为调用者负责清理栈,这种调用约定天然支持可变参数函数(如
printf)。 - C/C++默认调用约定(非类成员函数)。
内容:
- 函数实参在线程栈上按照从右至左的顺序依次压栈。
- 函数结果保存在寄存器EAX/AX/AL中
- 浮点型结果存放在寄存器ST0中
- 编译后的函数名前缀以一个下划线字符
- 调用者负责从线程栈中弹出实参(即清栈)
- 8比特或者16比特长的整形实参提升为32比特长。
- 受到函数调用影响的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
- 不受函数调用影响的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
- RET指令从函数被调用者返回到调用者(实质上是读取寄存器EBP所指的线程栈之处保存的函数返回地址并加载到IP寄存器)
优点:支持可变参数函数,适合需要灵活处理参数数量的函数。
缺点:栈清理由调用者负责,如果频繁调用函数,栈清理开销会较大。
stdcall(Standard Call)
Windows API 中的标准调用约定
特点:
- 参数从右到左压栈。
- 被调用函数自行清理栈。
- Windows API广泛使用(如
MessageBox)。
优点:减少了调用者的栈管理负担,代码更简洁。效率较高
缺点:不支持可变参数函数。
fastcall
- 特点:
- 前两个参数通过寄存器传递(如
ECX和EDX,具体依赖编译器)。 - 剩余参数从右到左压栈。
- 被调用函数清理栈。
- 目标:提高性能(减少栈操作)。
- 前两个参数通过寄存器传递(如
thiscall(C++类成员函数)
- 特点:
this指针通过寄存器传递(如ECX在MSVC中)。- 参数从右到左压栈(MSVC)或寄存器优先(GCC)。
- C++类成员函数的默认调用约定。
more
| 特性 | cdecl |
stdcall |
fastcall |
thiscall |
|---|---|---|---|---|
| 参数传递顺序 | 右→左(压栈) | 右→左(压栈) | 寄存器+栈 | 寄存器(this) + 栈 |
| 栈清理责任 | 调用者 | 被调用函数 | 被调用函数 | 被调用函数 |
| 适用场景 | 可变参数函数 | Windows API | 高性能函数 | C++成员函数 |
| 寄存器使用 | 无 | 无 | ECX, EDX (MSVC) | ECX (MSVC) |
影响
- 栈不平衡问题:如果调用者与被调用函数的调用约定不匹配(如误用
stdcall和cdecl),会导致栈指针错误,引发程序崩溃。 - 名称修饰差异:不同编译器对调用约定的名称修饰规则不同(如
_add@8对应stdcall)。 - 调试与逆向:在逆向工程中,识别调用约定可帮助理解参数传递和栈平衡逻辑。