C++函数调用
C++支持两种函数调用方式,__stdcall
和__cdecl
,两者对栈帧(stack frame)的处理方式不同,__stdcall
是由被调用函数来清理栈帧,__cdecl
是由调用函数来清理栈帧。一旦混用,容易引发错误
两者混用可能会导致栈帧没有被释放或者被连续释放两次
stack frame
栈帧用于维护函数调用的上下文信息,在函数调用时,会在栈上分配一块内存,用于存储函数的参数、返回值、局部变量、调用者的栈指针等,函数返回后,栈帧会被销毁
standard call
__stdcall
全称为standard call,是Pascal和Win32的默认调用方法,函数在调用时必须严格按照定义传递参数,参数从右向左入栈,在函数返回前执行出栈指令(retn x),清理栈帧
// windef.h |
extern "C" int __stdcall AddStdCall(int a, int b); |
C Declaration
__cdecl
全称为C Declaration,是C/C++的缺省调用方法,该方法最大的特点是允许传入可变参数,函数返回后由调用者执行出栈指令(ret),清理栈帧
extern "C" int __cdecl SubtractCDecl(int a, int b); |
封装
有的第三方库无可奈何地使用了__stdcall
,而我们的项目使用的是__cdecl
,这时候就需要对第三方库进行封装,以便能够正常调用
// 假设第三方库提供的接口声明为以下形式(使用 __stdcall 调用约定) |
// 封装类 |
Windows函数调用约定
以MSVC x64调用约定为例
x64 ABI使用四寄存器fast-call调用约定,在函数调用时将函数的参数存储在指定的寄存器中,参数和这些寄存器有着严格的对应关系。
- 一个参数最多只能放在一个寄存器中
- 若一个参数的大小不是1、2、4、8字节,将会按引用传递
寄存器中整数右对齐,被调用方可以忽略寄存器中的高位数据,于是可以向下兼容(即1、2、4、8可以放进8字节的寄存器中)
为什么是8字节呢?因为8字节=64位,x64系统的寄存器大小为64位
- 整数参数使用RCX、RDX、R8、R9寄存器传递
func1(int a, int b, int c, int d, int e, int f); |
- 浮点参数使用XMM0-XMM3寄存器传递
func2(float a, double b, float c, double d, float e, float f); |
整数和浮点数混合
func3(int a, double b, int c, float d, int e, float f); |