C++-调用约定

概念

函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。

函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。

C++ 中的常用函数调用约定有__cdecl__stdcall__fastcallthiscall__pascalnaked。另外也涵盖了MSVC独有的__clrcall__vectorcall

__stdcall

__stdcall 是 StandardCall 的缩写,是C++标准调用方式,又称为 Pascal 调用方式。

特点

  1. 参数自右向左入栈;
  2. 被调用函数清理堆栈;

__cdecl

__cdecl 是 C Declaration 的缩写,又称为C调用约定,是C语言缺省时的调用约定。

特点

  1. 参数自右向左入栈;
  2. 由调用者手动清栈;

由于由调用者清理栈,所以允许可变参数存在,如int sprintf(char* buffer, const char* format,...)

__fastcall

fastcall 从名字可以得知,是一种快速调用方式,它通过 CPU 寄存器传递参数。

特点

  1. 在自变量列表中按从左到右的顺序找到的前两个 DWORD 或更小自变量将在 ECX 和 EDX 寄存器中传递;所有其他自变量在堆栈上从右向左传递;
  2. 被调用函数清理堆栈;

__thiscall

thiscall 调用方式是唯一一种不能显示指定的修饰符。

它是C++ 类成员函数缺省的调用方式。由于成员函数调用还有一个this指针,因此必须用这种特殊的调用方式。

特点

  1. 参数自右向左入栈;
  2. 如果参数个数确定,this指针通过ecx传递给被调用者,且由被调用函数清理堆栈;
  3. 如果参数个数不确定,this指针在所有参数压入栈后被压入栈,且由调用者清理堆栈。
    可以看到,如果参数个数确定,类似__stdcall,如若不是则类似cdecl

__pascal

pascal 是 Pascal 语言(Delphi)的函数调用方式

特点

  1. 参数自左向右入栈;
  2. 被调用函数清理堆栈。

naked

对于使用 属性声明 naked 的函数,编译器将生成不带 prolog 和 epilog 代码的代码。

利用此功能,可以使用内联汇编程序代码编写您自己的 prolog/epilog 代码序列。naked 函数对于编写虚拟设备驱动程序特别有用。

请注意,该属性 naked 仅在 x86 和 ARM 上有效,在 x64 上不可用。

特点

  1. 不允许使用 return 语句;
  2. 使用 naked 进行编译时,将忽略 naked 关键字;
  3. 对于 __fastcall 函数,只要 C/C++ 代码中引用了其中一个寄存器参数,prolog 代码就会将寄存器的值存储到该变量的堆栈位置。

__clrcall(Microsoft 专用)

clrcall 指定只能从托管代码调用的函数。

clrcall 函数指针只能在创建函数指针的应用程序域中使用。

__vectorcall(Microsoft 专用)

目的为优化浮点运算,__vectorcall 继承自 __fastcall,即对于 fastcall 规则中的整数仍然按 fastcall 中的规则传递,而浮点及向量则通过寄存器传递。

修饰名

C 编译器中

  1. __stdcall,函数名前缀_,后缀@再跟上其参数的十进制字节数,格式为_funcName@paramSize;如Func(int a, double b),修饰名为_Func@12;
  2. __cdecl,函数名前缀_,无后缀,格式为_funcName;如Func(int a, double b),修饰名为_Func;
  3. __fastcall,函数名前缀@,后缀@再跟上其参数的十进制字节数,格式为@funcName@paramSize;如Func(int a, double b),修饰名@Func@12

C++ 编译器中:

C++ 编译器中,修饰规则更为复杂但也更规范化,通过分析能够获得调用方式,返回值类型,参数个数以及参数类型。

不论是__stdcall,__cdecl还是__fastcall,格式都为?funcName%参数表开始标识%%参数表%%参数数量标识%;

%参数表开始标识%

对于__stdcall方式是@@YG;
对于__cdecl方式是@@YA;
对于__fastcall方式是@@YI

%参数表代号%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- X - void
- D - char
- E - unsigned char
- F - short
- H - int
- I - unsigned int
- J - long
- K - unsigned long
- M - float
- N - double
- _N - bool
// 结构体类型,后跟结构体类型名,用`@@`表示结束
- U - struct
// 指针类型,后面的代号表明指针类型,如果相同指针重复出现则以`0`代替,一个`0`代表一次重复
- PA - 指针
- PB - const 指针

参数表的第一项为该函数的返回值类型,其后分别为参数的数据类型,指针标识在所指数据类型前;

%参数数量标识%

如果该函数无参,则以Z标识结束;

否则参数表后以@Z标识为整个名字的结束。

格式及例子

格式为?funcName@@YG****@Z或者?funcName@@YG*XZ

  1. int Func1(char* c, unsinged long),修饰名为?Func1@@YGHPADK@Z
  2. 又如void Func2(),修饰名为?Func2@@YGXXZ

类成员函数

对于 C++ 的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同;

  1. 首先就是在函数名字和参数表之间插入以@字符引导的类名;
  2. 其次是参数表的开始标识不同,public 成员函数的标识是@@QAE;protected 成员函数的标识是@@IAE;private 成员函数的标识是@@AAE
  3. 如果函数声明使用了 const 关键字,则相应的标识应分别为@@QBE@@IBE@@ABE
  4. 如果参数类型是类实例的引用,则使用AAV1,对于const类型的引用,则使用ABV1

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct HandleTest__
{
int a;
char b;
} *HandleTest;
class NameTest
{
private:
char PriFunc(int num);
protected:
void ProFunc(const NameTest& src);
public:
short PubFunc1(HandleTest handle, long pos,const char* src, bool flag);
long PubFunc2(unsigned long pos) const;
}

对于PriFunc,其修饰名为?PriFunc@NameTest@@AAEDH@Z

对于ProFunc,其修饰名为?ProFunc@NameTest@@IAEXABV!@@Z

对于PubFunc1,其修饰名为?PubFunc1@NameTest@@QAEFPAUHandleTest__@@JPBD_N@Z

对于PubFunc2,其修饰名为?PubFunc2@NameTest@@QBEJK@Z