函数调用约定是指对函数调用的约束和规范,主要包括三个方面
站在用户的角度思考问题,与客户深入沟通,找到贵定网站设计与贵定网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:做网站、成都网站设计、企业官网、英文网站、手机端网站、网站推广、空间域名、网络空间、企业邮箱。业务覆盖贵定地区。
- 函数参数压栈的顺序
- 参数出栈由调用者还是被调用者负责
- 函数名字修饰的方式
函数名修饰是编译器在编译阶段创建的字符串用于标识函数的定义或原型,在链接程序或者一些工具中需要根据函数名字修饰来定位函数的具体位置
C++中为重载函数以及一些特殊函数(构造函数、析构函数)指定名字修饰,在汇编程序中通过函数名修饰来调用C/C++函数
不同高级语言中使用的函数调用约定不同,这里以C/C++为例
C :__cdecl、__stdcall、__fastcall、naked、__pascal
C++:__cdecl、__stdcall、__fastcall、naked、__pascal、__thiscall
__cdeclC/C++缺省的调用约定(c declaration),又被称为Pascal调用
声明语法:int __cdecl func(int, double)
- 参数压栈顺序:从右到左
- 参数出栈(恢复栈顶指针ESP):由调用者负责
- 函数名修饰方式:
C:在函数名前加上下划线前缀,即_func
C++:函数名前加“?”前缀,函数名后加上@@YA和相关标识,即?func@@YAHHN@Z(具体的标识下文会列出
__stdcall标准调用约定(standard call),又被称为Pascal调用,Windows API使用此种调用方式
声明语法:int __stdcall func(int, double)
- 参数压栈顺序:从右到左
- 参数出栈:由被调用者负责
- 函数名修饰方式:
C:在函数名前加上下划线前缀,函数名后加上@和所有参数所占字节大小之和,即_func@12(本文以x64为例)
C++:函数名前加“?”,函数名后加上@@YG和相关参数标识,即?func@@YGHHN@Z
__fastcall快速调用约定(fast call),利用寄存器传递参数,将前两个DWORD或者更小的参数传入到ECX和EDX中,其余参数按从右到左压栈,参数的返回结果会传入EAX
声明语法:int __fastcall func(int, double)
- 参数压栈顺序:从右到左
- 参数出栈:由被调用者负责
- 函数名修饰方式:
C:在函数名前加上@前缀,函数名后加上@和所有参数所占字节大小之和,即@func@12
C++:函数名前加“?”,函数名后加上@@YI和相关参数标识,即?func@@YIHHN@Z
__thiscallthis指针调用约定,C++成员函数缺省的调用约定,不能显式指明
- 参数压栈顺序:从右到左
若参数数量确定,则将this指针存入ECX再传递给被调用者,参数从右到左压栈
若参数数量不确定,则先将参数从右到左压栈,然后再将this指针压栈
- 参数出栈:
若参数数量确定,则由被调用者负责
若参数数量不确定,则由调用者负责
- 函数名修饰方式(见下文)
naked裸函数调用,具体使用规则可以参考裸函数的规则和限制 | Microsoft Learn
声明语法:__declspec(naked) int func(int, double)
__pascal声明语法:int __pascal func(int, double)
- 参数压栈顺序:从右到左
- 参数出栈:由被调用者负责
该约定方式已经被弃用,一般用__stdcall来代替
__cdecl与__stdcall/__fastcall调用约定对比,大的不同在于参数出栈的控制方不同。__stdcall/__fastcall参数出栈由被调用方负责,即参数的出栈由被调用的函数负责,但是当函数为可变参函数,函数无法正确处理参数出栈,此时应该由函数的调用方来控制参数出栈,因此可变参函数应该指明为__cdecl
C/C++函数名修饰规则__cdecl | __stdcall | __fastcall | |
C | "_" + 函数名 函数名加上“_”前缀,即_func | "_" + 函数名 + "@" + 参数字节大小 函数名加上“_”前缀,函数名后加上“@”和所有参数字节大小之和,即_func@21 | "@" + 函数名 + "@" + 参数字节大小 函数名加上"@"前缀,函数名后加上“@”和所有参数字节大小之和,即@func@21 |
C++ | "?" + 函数名 + "@@YA" + 相关标识 函数名加上“?”前缀,函数名后加上“@@YA”和相关标识,即?func@@YAH_NHPEANPEBD@Z | "?" + 函数名 + "@@YG" + 相关标识 函数名加上“?”前缀,函数名后加上“@@YG”和相关标识,即?func@@YGH_NHPEANPEBD@Z | "?" + 函数名 + "@@YI" + 相关标识 函数名加上“?”前缀,函数名后加上“@@YI”和相关标识,即?func@@YI_NHPEANPEBD@Z |
注:上表以int func(bool, int, double*, const char*)为例
C++中三种函数名修饰主要区别在于后缀开始标识”@@YA”、“@@YG”、“@@YI”,其余标识修饰规则一致
上文提到的相关标识主要由三部分组成,相关标识 = 返回值标识+参数标识+结束标识,部分类型标识映射关系如下(基于VisualStudio 2022)
void | X |
char | D |
unsigned char | E |
short | F |
unsigned short | G |
int / __int32 | H |
unsigned int | I |
long | J |
unsigned long | K |
long long / __int64 | _J |
float | M |
double | N |
bool | _N |
struct | U |
* | PEA |
const* | PEB |
& | AEA |
const& | AEB |
struct | U |
class | V |
具体规则(以__cdecl为例):
struct和class作为参数时,由标识+结构/类名+"@@"组成,
对于结构体Data,Data data对应的参数标识为UData@@;对于类CTest,CTest t对应的参数标识为VCTest@@
当有连续的相同类型的参数时,第二个开始的参数以“0”作为标识,但是在&,const&,*,const*时会有根据具体的顺序将值递增
void func(CTest t1, CTest t2, CTest t3, int a, CTest t4) → ?func@@YAXVTest@@00HVTest@@@Z
void func(CTest t1, CTest t2, CTest& t3, CTest& t4) → ?func@@YAXVTest@@00AEAV1@1@Z
若函数无参数则结束标识为“Z”,有参数则为“@Z”
void func() → ?func@@YAXXZ
void func(int) → ?func@@YAXH@Z
注:不同编译器的修饰规则不一定相同,可以在cmd通过dumpbin从obj或lib中获取程序中函数的修饰名,具体命令为dumpbin /symbols [filename]
__thiscall函数名修饰规则"?" + 函数名 + "@" + 类名 + 访问权限标识 + 相关标识,其中相关标识规则如__cdel/__stdcall/__fastcall
访问权限标识映射如下
public | @@QAE |
protected | @@IAE |
private | @@AAE |
示例:
class CTest
{
public:
void setA(int a);
int getA() const;
protected:
void check();
private:
void testInfo(const CTest& t);
};
setA → ?setA@CTest@@QAEXH@Z
check → ?check@CTest@@IAEXXZ
testInfo → ?testInfo@CTest@@AAEXAEBV1@@Z
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧