函数调用约定cdecl、stdcall、fastcall

我们在编写代码的时候都会调用函数,有点函数有多个参数,例如:

int test(int a,char b, char* c);

上面的函数调用方式是test(10, ‘c’, “tinus”);那么这个函数编译器是怎么知道有多少个参数,参数类型是什么了?因为函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。那么在参数传递中当参数个数多于一个时,按照什么顺序把参数压入堆栈,并且函数调用后,栈里面的数据由谁来释放处理?

为了解决上面的问题,C/C++通过函数调用约定来说明这些问题。


函数调用约定

是函数调用者和被调用的函数体之间关于参数传递、返回值传递、堆栈清除、寄存器使用的一种约定。
常见的调用约定有:

    stdcall(pascal)

    cdecl

    fastcall

    thiscall
  • 调用协议常用场合
    1. __stdcall: stdcall(pascal)–Standard Call的缩写,C++的标准调用方式,在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。一般WIN32的函数都是__stdcall。
    2. __cdecl:cdecl–C Declaration的缩写,C语言缺省的调用约定,C/C++默认的函数调用协议。
    3. __fastcall:适用于对性能要求较高的场合。
    4. __thiscall :C++类成员函数缺省的调用约定。thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。由于成员函数调用还有一个this指针,因此必须特殊处理
  • 函数参数入栈方式
    1. __stdcall:函数参数由右向左入栈。 对于参数个数固定情况下,类似于stdcall,不定时则类似cdecl。
    2. __cdecl:函数参数由右向左入栈。 对于参数个数固定情况下,类似于stdcall,不定时则类似cdecl。
    3. __fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。
    4. __thiscall :参数从右向左入栈,如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。如果参数个数确定,this指针通过ecx传递给被调用者。
  • 栈内数据清除方式
    1. __stdcall:函数调用结束后由被调用函数清除栈内数据。
    2. __cdecl:函数调用结束后由函数调用者清除栈内数据。
    3. __thiscall :如果参数个数不确定,调用者清理堆栈,否则函数自己清理堆栈
    4. __fastcall:函数调用结束后由被调用函数清除栈内数据。
    5. 问题一:不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。
    6. 问题二:某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。
    7. 问题三:由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。
  • C语言编译器函数名称修饰规则
    1. __stdcall:编译后,函数名被修饰为“_functionname@number”。
    2. __cdecl:编译后,函数名被修饰为“_functionname”。
    3. __fastcall:编译后,函数名给修饰为“@functionname@nmuber”。
    4. 注:“functionname”为函数名,“number”为参数字节数。
    5. 注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
  • C++语言编译器函数名称修饰规则
    1. __stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。
    2. __cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。
    3. __fastcall:编译后,函数名被修饰为“?functionname@@YI******@Z”。
    4. 注:“******”为函数返回值类型和参数类型表。
    5. 注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
    6. C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。

具体可以查看下表:

C++编译时函数名修饰约定规则:

    __stdcall调用约定:

        以“?”标识函数名的开始,后跟函数名;

        函数名后面以“@@YG”标识参数表的开始,后跟参数表;

        参数表以代号表示:

            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,

            PA–表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复
            …

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

        参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。

    例如:

    int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
    void Test2() -----“?Test2@@YGXXZ”

    __cdecl调用约定:
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。

    __fastcall调用约定:
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。

函数调用约定导致的常见问题

如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:

    函数原型声明和函数体定义不一致
    DLL导入函数时声明了不同的函数约定

调用函数的代码和被调函数必须采用相同的函数的调用约定,程序才能正常运行。

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值