函数调用约定(Calling Convention)指的是在函数调用时,参数的传递方式、函数返回值的处理方式以及调用者与被调用者之间如何维护堆栈等规则。不同的调用约定在这些方面有不同的实现方式,主要影响到函数的跨平台兼容性、性能优化以及编译器的行为。
以下是几种常见的函数调用约定:
1. __cdecl
(C Declaration)
- 参数传递: 参数从右到左依次压入堆栈。
- 堆栈清理: 调用者负责清理堆栈。这意味着同样的函数可以有不同数量的参数(可变参数)。
- 名称修饰: 函数名称前加上
_
前缀(如_func
)。 - 使用场景: 这是 C 和 C++ 中的默认调用约定,特别是涉及可变参数的函数,如
printf
、scanf
。 - 优点: 灵活,可支持可变参数函数。
- 缺点: 堆栈管理的开销由调用者承担,在频繁调用时可能会影响性能。
2. __stdcall
(Standard Call)
- 参数传递: 参数从右到左依次压入堆栈。
- 堆栈清理: 被调用的函数负责清理堆栈。
- 名称修饰: 函数名称前加上
_
前缀,后跟@
和参数字节数(如_func@12
)。 - 使用场景: Windows API 大部分函数使用此调用约定。
- 优点: 堆栈清理由被调用者负责,调用者不必担心堆栈的清理,可以简化调用者的代码。
- 缺点: 不支持可变参数函数。
3. __fastcall
(Fast Call)
- 参数传递: 前两个参数通过寄存器(通常是 ECX 和 EDX)传递,其余参数从右到左压入堆栈。
- 堆栈清理: 被调用者负责清理堆栈。
- 名称修饰: 函数名称前加上
@
前缀,后跟@
和参数字节数(如@func@8
)。 - 使用场景: 在对性能要求高的场合,因为通过寄存器传递参数可以减少压栈和出栈操作,提升函数调用的速度。
- 优点: 提高了函数调用的效率,特别是传递少量参数时。
- 缺点: 不同的编译器可能对
__fastcall
的实现有所不同,可能影响代码的可移植性。
4. __thiscall
(This Call)
- 参数传递: 用于 C++ 成员函数调用,第一个参数(
this
指针)通过 ECX 寄存器传递,其余参数从右到左压入堆栈。 - 堆栈清理: 被调用者负责清理堆栈。
- 名称修饰: 编译器自动处理,依赖于具体的实现,通常没有统一的名称修饰规则。
- 使用场景: C++ 类的成员函数。
- 优点: 专为成员函数设计,自动处理
this
指针。 - 缺点: 仅用于成员函数,不能作为一般的调用约定使用。
5. __vectorcall
- 参数传递: 参数优先通过 SIMD 寄存器(如 XMM、YMM)传递,特别适合传递矢量和浮点数类型参数。
- 堆栈清理: 被调用者负责清理堆栈。
- 名称修饰: 与
__fastcall
类似。 - 使用场景: 适合涉及大量浮点计算或矢量运算的函数。
- 优点: 提高了浮点和矢量运算的效率。
- 缺点: 对硬件要求较高(需要支持 SIMD 指令集),可移植性较差。
6. __declspec(naked)
- 功能: 裸函数,编译器不会为函数生成任何标准的函数序言(Prologue)和尾声(Epilogue)代码,适用于非常底层的编程,如编写汇编代码。
- 使用场景: 用于内联汇编或极端性能优化场景。
- 优点: 提供了完全控制函数调用的能力。
- 缺点: 使用复杂且容易出错,通常需要手动管理堆栈。