1 前言
在C和C++编程中,函数调用约定(Calling Convention)定义了函数参数如何传递、由谁负责清理栈(如果有的话)、以及返回值如何传递等规则。不同的平台和编译器可能采用不同的调用约定。以下是一些常见的调用约定及其特点的详细说明:
2 __cdecl
- 定义与特点:
__cdecl是C语言默认的调用约定,在C++中也可使用,但不是类成员函数的默认调用约定。
参数从右向左压入堆栈。
调用者负责清理堆栈,这意味着调用者需要知道传递了多少参数,并据此调整堆栈指针。
支持可变参数列表的函数(如printf)必须使用此调用约定。
函数名在编译后通常不会改变(尽管在某些编译器中可能会添加前缀,如下划线)。 - 适用场景:
C语言编写的程序。
在Windows上用于一些API函数(尽管Windows API主要使用__stdcall)。
需要实现可变参数列表的函数。
3 __stdcall
- 定义与特点:
__stdcall是Windows API中广泛使用的调用约定。
参数也是从右向左压入堆栈,但与被调用者(callee)负责清理堆栈的__cdecl不同,__stdcall由被调用者清理堆栈。
函数名在编译时会加上一个特定的后缀(如“_”前缀和参数总字节大小的“@”后缀),以区分调用约定。
由于被调用者负责清理堆栈,这有助于减少调用者的工作量,并允许被调用者知道需要清理多少堆栈空间。 - 适用场景:
Windows API函数。
需要跨语言或跨平台调用时,因为许多其他语言和平台都支持或能够模拟__stdcall调用约定。
4 __fastcall
- 定义与特点:
__fastcall是一种通过寄存器快速传递参数的调用约定。
通常,函数的第一个和第二个参数(或更小的参数)通过特定的寄存器(如ECX和EDX)传递,其他参数则从右向左压入堆栈。
被调用者负责清理堆栈。
由于使用了寄存器传递参数,这种调用约定通常比仅使用堆栈的调用约定更快。 - 适用场景:
需要提高函数调用性能的场景。
某些特定的编译器或平台可能默认使用此调用约定(如Borland C++ Builder)。
5 __thiscall
- 定义与特点:
__thiscall是C++中类成员函数的默认调用约定(但请注意,这并非所有编译器都遵循此约定,且__thiscall不是一个关键字,而是一个描述性的名称)。
在这种调用约定中,“this”指针隐式地作为第一个参数通过寄存器(如ECX)传递给函数。
其他参数从右向左压入堆栈。
被调用者负责清理堆栈(尽管在某些情况下,如果参数个数不确定,调用者可能需要清理堆栈)。 - 适用场景:
C++类成员函数。
6 Naked Call
- 定义与特点:
Naked Call是一种非常底层的调用约定,它不生成任何prolog(函数开头的保存寄存器代码)或epilog(函数结尾的恢复寄存器代码)代码。
程序员需要手动编写这些代码,以控制函数的入口和出口。
这种调用约定通常用于需要精确控制函数行为或与其他低级代码(如汇编语言)紧密集成的场景。 - 适用场景:
编写需要与汇编语言代码紧密集成的函数。
需要完全控制函数调用的堆栈和寄存器行为的场景。
7 总结
不同的调用约定适用于不同的场景和需求。了解这些调用约定的特点和适用场景有助于编写更高效、更可移植的代码。在选择调用约定时,需要考虑目标平台、编译器、以及与其他代码或库的兼容性。