概念
函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。
函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。
C++ 中的常用函数调用约定有__cdecl
,__stdcall
,__fastcall
,thiscall
,__pascal
与naked
。另外也涵盖了MSVC独有的__clrcall
与__vectorcall
。
__stdcall
__stdcall 是 StandardCall 的缩写,是C++标准调用方式,又称为 Pascal 调用方式。
特点
- 参数自右向左入栈;
- 被调用函数清理堆栈;
__cdecl
__cdecl 是 C Declaration 的缩写,又称为C调用约定,是C语言缺省时的调用约定。
特点
- 参数自右向左入栈;
- 由调用者手动清栈;
由于由调用者清理栈,所以允许可变参数存在,如int sprintf(char* buffer, const char* format,...)
。
__fastcall
fastcall 从名字可以得知,是一种快速调用方式,它通过 CPU 寄存器传递参数。
特点
- 在自变量列表中按从左到右的顺序找到的前两个 DWORD 或更小自变量将在 ECX 和 EDX 寄存器中传递;所有其他自变量在堆栈上从右向左传递;
- 被调用函数清理堆栈;
__thiscall
thiscall 调用方式是唯一一种不能显示指定的修饰符。
它是C++ 类成员函数缺省的调用方式。由于成员函数调用还有一个this指针,因此必须用这种特殊的调用方式。
特点
- 参数自右向左入栈;
- 如果参数个数确定,this指针通过ecx传递给被调用者,且由被调用函数清理堆栈;
- 如果参数个数不确定,this指针在所有参数压入栈后被压入栈,且由调用者清理堆栈。
可以看到,如果参数个数确定,类似__stdcall
,如若不是则类似cdecl
。
__pascal
pascal 是 Pascal 语言(Delphi)的函数调用方式
特点
- 参数自左向右入栈;
- 被调用函数清理堆栈。
naked
对于使用 属性声明 naked 的函数,编译器将生成不带 prolog 和 epilog 代码的代码。
利用此功能,可以使用内联汇编程序代码编写您自己的 prolog/epilog 代码序列。naked 函数对于编写虚拟设备驱动程序特别有用。
请注意,该属性 naked 仅在 x86 和 ARM 上有效,在 x64 上不可用。
特点
- 不允许使用 return 语句;
- 使用 naked 进行编译时,将忽略 naked 关键字;
- 对于 __fastcall 函数,只要 C/C++ 代码中引用了其中一个寄存器参数,prolog 代码就会将寄存器的值存储到该变量的堆栈位置。
__clrcall(Microsoft 专用)
clrcall 指定只能从托管代码调用的函数。
clrcall 函数指针只能在创建函数指针的应用程序域中使用。
__vectorcall(Microsoft 专用)
目的为优化浮点运算,__vectorcall 继承自 __fastcall,即对于 fastcall 规则中的整数仍然按 fastcall 中的规则传递,而浮点及向量则通过寄存器传递。
修饰名
C 编译器中
- __stdcall,函数名前缀
_
,后缀@
再跟上其参数的十进制字节数,格式为_funcName@paramSize
;如Func(int a, double b)
,修饰名为_Func@12
; - __cdecl,函数名前缀
_
,无后缀,格式为_funcName
;如Func(int a, double b)
,修饰名为_Func
; - __fastcall,函数名前缀
@
,后缀@
再跟上其参数的十进制字节数,格式为@funcName@paramSize
;如Func(int a, double b)
,修饰名@Func@12
。
C++ 编译器中:
C++ 编译器中,修饰规则更为复杂但也更规范化,通过分析能够获得调用方式,返回值类型,参数个数以及参数类型。
不论是__stdcall,__cdecl还是__fastcall,格式都为?funcName%参数表开始标识%%参数表%%参数数量标识%
;
%参数表开始标识%
对于__stdcall方式是@@YG
;
对于__cdecl方式是@@YA
;
对于__fastcall方式是@@YI
。
%参数表代号%
1 | - X - void |
参数表的第一项为该函数的返回值类型,其后分别为参数的数据类型,指针标识在所指数据类型前;
%参数数量标识%
如果该函数无参,则以Z
标识结束;
否则参数表后以@Z
标识为整个名字的结束。
格式及例子
格式为?funcName@@YG****@Z
或者?funcName@@YG*XZ
;
- 如
int Func1(char* c, unsinged long)
,修饰名为?Func1@@YGHPADK@Z
- 又如
void Func2()
,修饰名为?Func2@@YGXXZ
类成员函数
对于 C++ 的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同;
- 首先就是在函数名字和参数表之间插入以
@
字符引导的类名; - 其次是参数表的开始标识不同,public 成员函数的标识是
@@QAE
;protected 成员函数的标识是@@IAE
;private 成员函数的标识是@@AAE
; - 如果函数声明使用了 const 关键字,则相应的标识应分别为
@@QBE
,@@IBE
和@@ABE
; - 如果参数类型是类实例的引用,则使用
AAV1
,对于const类型的引用,则使用ABV1
。
例子
1 | typedef struct HandleTest__ |
对于PriFunc
,其修饰名为?PriFunc@NameTest@@AAEDH@Z
;
对于ProFunc
,其修饰名为?ProFunc@NameTest@@IAEXABV!@@Z
;
对于PubFunc1
,其修饰名为?PubFunc1@NameTest@@QAEFPAUHandleTest__@@JPBD_N@Z
;
对于PubFunc2
,其修饰名为?PubFunc2@NameTest@@QBEJK@Z
。