函数调用:__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++默认调用约定(非类成员函数)。

内容:

  1. 函数实参在线程栈上按照从右至左的顺序依次压栈。
  2. 函数结果保存在寄存器EAX/AX/AL中
  3. 浮点型结果存放在寄存器ST0中
  4. 编译后的函数名前缀以一个下划线字符
  5. 调用者负责从线程栈中弹出实参(即清栈)
  6. 8比特或者16比特长的整形实参提升为32比特长。
  7. 受到函数调用影响的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
  8. 不受函数调用影响的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
  9. RET指令从函数被调用者返回到调用者(实质上是读取寄存器EBP所指的线程栈之处保存的函数返回地址并加载到IP寄存器)

优点:支持可变参数函数,适合需要灵活处理参数数量的函数。

缺点:栈清理由调用者负责,如果频繁调用函数,栈清理开销会较大。

stdcall(Standard Call)

Windows API 中的标准调用约定

特点

  • 参数从右到左压栈。
  • 被调用函数自行清理栈
  • Windows API广泛使用(如MessageBox)。

优点:减少了调用者的栈管理负担,代码更简洁。效率较高

缺点:不支持可变参数函数。

fastcall

  • 特点
    • 前两个参数通过寄存器传递(如ECXEDX,具体依赖编译器)。
    • 剩余参数从右到左压栈。
    • 被调用函数清理栈。
    • 目标:提高性能(减少栈操作)。

thiscall(C++类成员函数)

  • 特点
    • this指针通过寄存器传递(如ECX在MSVC中)。
    • 参数从右到左压栈(MSVC)或寄存器优先(GCC)。
    • C++类成员函数的默认调用约定。

more

特性 cdecl stdcall fastcall thiscall
参数传递顺序 右→左(压栈) 右→左(压栈) 寄存器+栈 寄存器(this) + 栈
栈清理责任 调用者 被调用函数 被调用函数 被调用函数
适用场景 可变参数函数 Windows API 高性能函数 C++成员函数
寄存器使用 ECX, EDX (MSVC) ECX (MSVC)

影响

  • 栈不平衡问题:如果调用者与被调用函数的调用约定不匹配(如误用stdcallcdecl),会导致栈指针错误,引发程序崩溃。
  • 名称修饰差异:不同编译器对调用约定的名称修饰规则不同(如_add@8对应stdcall)。
  • 调试与逆向:在逆向工程中,识别调用约定可帮助理解参数传递和栈平衡逻辑。