C/C++函数调用规约

函数调用约定是指对函数调用的约束和规范,主要包括三个方面

  • 函数参数压栈的顺序
  • 参数出栈由调用者还是被调用者负责
  • 函数名字修饰的方式

函数名修饰是编译器在编译阶段创建的字符串用于标识函数的定义或原型,在链接程序或者一些工具中需要根据函数名字修饰来定位函数的具体位置

C++中为重载函数以及一些特殊函数(构造函数、析构函数)指定名字修饰,在汇编程序中通过函数名修饰来调用C/C++函数

不同高级语言中使用的函数调用约定不同,这里以C/C++为例

C     :__cdecl、__stdcall、__fastcall、naked、__pascal

C++:__cdecl、__stdcall、__fastcall、naked、__pascal、__thiscall

__cdecl

        C/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

__thiscall

            this指针调用约定,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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值