C/C++ 函数调用约定

C/C++ 函数调用约定,主要是对以下三个方面进行了约定:

  1. 当参数个数多于一个时,按照什么顺序把参数压入堆栈。– 调用函数时,参数的入栈顺序。
  2. 函数调用后,由谁来把堆栈恢复原状。– 调用结束后,由谁(调用者还是被调用者)负责将参数出栈。
  3. 函数编译后的命名规则(Name Mangling)。

常见的调用方式有:__cdecl、__stdcall、__fastcall、__thiscall。

__stdcall

stdcall(StandardCall)是C++的标准调用方式。stdcall 调用方式又被称为 Pascal 调用方式。在Microsoft C++系列的C/C++编译器中,使用 PASCAL 宏,WINAPI 宏和 CALLBACK 宏来指定函数的调用方式为 stdcall。

stdcall的调用规则:

  1. 参数从右向左依次压入堆栈.
  2. 由被调用函数自己来恢复堆栈,称为自动清栈。
  3. 函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的大小。

__cdecl

cdecl(C declaration),是 C/C++ 函数默认的调用规范。

cdecl的调用规则:

  1. 参数从右向左依次压入堆栈。
  2. 由调用者恢复堆栈,称为手动清栈。
  3. 函数名自动加前导下划线。

__fastcall:

顾名思义调用时会比其它调用约定快一点,因为其参数会利用寄存器进行传递。

fastcall 的调用规则:

  1. 若有多个参数,寄存器不够了,其余参数会从右向左入栈。
  2. 由被调用函数恢复栈顶指针。
  3. 编译后函数名前和函数名后都会会加符号@,紧接着再加参数字节数。

__thiscall

thiscall 调用方式是唯一一种不能显示指定的修饰符。它是C++非静态成员函数缺省的调用方式。由于成员函数调用还有一个this指针,因此必须用这种特殊的调用方式。

thiscall的调用规则:

  1. 参数从右向左压入栈。
  2. 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压入栈后被压入栈。参数个数不定的,由调用者清理堆栈,否则由函数自己清理堆栈。可以看到,对于参数个数固定的情况,它类似于stdcall,不定时则类似于cdecl。

__stdcall、__cdecl 和 __fastcall 对比

  1. 调用协议常用场合

__stdcall:Windows API默认的函数调用协议。
__cdecl:C/C++默认的函数调用协议。
__fastcall:适用于对性能要求较高的场合。

  1. 函数参数入栈方式

__stdcall:函数参数由右向左入栈。
__cdecl:函数参数由右向左入栈。
__fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。

__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。

  1. 栈内数据清除方式

__stdcall:函数调用结束后由被调用函数清除栈内数据。
__cdecl:函数调用结束后由函数调用者清除栈内数据。
__fastcall:函数调用结束后由被调用函数清除栈内数据。

不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。
某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。
由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。

  1. C语言编译器函数名称修饰规则

__stdcall:编译后,函数名被修饰为“_functionname@number”。
__cdecl:编译后,函数名被修饰为“_functionname”。
__fastcall:编译后,函数名给修饰为“@functionname@nmuber”。

注:“functionname”为函数名,“number”为参数字节数。

函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。

参考

https://zh.wikipedia.org/wiki/Visual_C%2B%2B%E5%90%8D%E5%AD%97%E4%BF%AE%E9%A5%B0