抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

C++函数调用

C++支持两种函数调用方式,__stdcall__cdecl,两者对栈帧(stack frame)的处理方式不同,__stdcall是由被调用函数来清理栈帧,__cdecl是由调用函数来清理栈帧。一旦混用,容易引发错误

两者混用可能会导致栈帧没有被释放或者被连续释放两次

stack frame

栈帧用于维护函数调用的上下文信息,在函数调用时,会在栈上分配一块内存,用于存储函数的参数、返回值、局部变量、调用者的栈指针等,函数返回后,栈帧会被销毁

standard call

__stdcall全称为standard call,是Pascal和Win32的默认调用方法,函数在调用时必须严格按照定义传递参数,参数从右向左入栈,在函数返回前执行出栈指令(retn x),清理栈帧

// windef.h
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#define cdecl _cdecl
#ifndef CDECL
#define CDECL _cdecl
#endif
extern "C" int __stdcall AddStdCall(int a, int b);

int main() {
int result = AddStdCall(5, 3); // 使用stdcall调用
std::cout << "Result using stdcall: " << result << std::endl;

return 0;
}

int __stdcall AddStdCall(int a, int b) {
return a + b;
}

C Declaration

__cdecl全称为C Declaration,是C/C++的缺省调用方法,该方法最大的特点是允许传入可变参数,函数返回后由调用者执行出栈指令(ret),清理栈帧

extern "C" int __cdecl SubtractCDecl(int a, int b);

int main() {
int result = SubtractCDecl(10, 4); // 使用cdecl调用
std::cout << "Result using cdecl: " << result << std::endl;
return 0;
}

int __cdecl SubtractCDecl(int a, int b) {
return a - b;
}

封装

有的第三方库无可奈何地使用了__stdcall,而我们的项目使用的是__cdecl,这时候就需要对第三方库进行封装,以便能够正常调用

// 假设第三方库提供的接口声明为以下形式(使用 __stdcall 调用约定)
extern "C" {
int __stdcall ThirdPartyFunction(int a, int b);
}
// 封装类
class ThirdPartyWrapper {
public:
static int CallFunction(int a, int b) {
return ThirdPartyFunction(a, b); // 调用第三方库函数
}
};

int main() {
int result = ThirdPartyWrapper::CallFunction(5, 3);
std::cout << "Result using third-party wrapper: " << result << std::endl;
return 0;
}

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);
// a in RCX, b in RDX, c in R8, d in R9, f then e pushed on stack
  • 浮点参数使用XMM0-XMM3寄存器传递
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e pushed on stack

整数和浮点数混合

func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e pushed on stack
triangle

评论