函数调用约定:__stdcall、__cdecl和__fastcall介绍

简介: 函数调用约定:__stdcall、__cdecl和__fastcall介绍

想深入了解可学习汇编语言去了解内存机制


一、问题引导:C语言中函数参数的入栈顺序


C程序栈底为高地址,栈顶为低地址。


C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数。通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。


因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。


二、、函数调用约定(Calling Convention)


函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。函数调用约定有很多方式,除了常见的__cdecl,__fastcall和__stdcall之外,C++的编译器还支持thiscall方式,不少C/C++编译器还支持 naked call方式。这么多函数调用约定常常令许多程序员很迷惑,到底它们是怎么回事,都是在什么情况下使用呢?下面就分别介绍这几种函数调用约定。


1.__cdecl


编译器的命令行参数是/Gd.__cdecl方式是C/C++编译器默认的函数调用约定,所有非C++成员函数和那些没有用__stdcall或__fastcall声明的函数都默认是__cdecl方式,它使用C函数调用方式,函数参数按照从右向左的顺序入栈,函数调用者负责清除栈中的参数,由于每次函数调用都要由编译器产生清除(还原)堆栈的代码,所以使用__cdecl方式编译的程序比使用__stdcall方式编译的程序要大很多,但是 __cdecl调用方式是由函数调用者负责清除栈中的函数参数,所以这种方式支持可变参数,比如printf和windows的API wsprintf就是__cdecl调用方式。对于C函数,__cdecl方式的名字修饰约定是在函数名称前添加一个下划线;对于C++函数,除非特别使用extern “C”,C++函数使用不同的名字修饰方式。


2.__fastcall


编译器的命令行参数是/Gr.__fastcall函数调用约定在可能的情况下使用寄存器传递参数,通常是前两个 DWORD类型的参数或较小的参数使用ECX和EDX寄存器传递,其余参数按照从右向左的顺序入栈,被调用函数在返回之前负责清除栈中的参数。编译器使用两个@修饰函数名字,后跟十进制数表示的函数参数列表大小,例如:@function_name@number.需要注意的是__fastcall函数调用约定在不同的编译器上可能有不同的实现,比如16位的编译器和32位的编译器,另外,在使用内嵌汇编代码时,还要注意不能和编译器使用的寄存器有冲突。


3.__stdcall


编译器的命令行参数是/Gz,__stdcall是Pascal程序的缺省调用方式,大多数Windows的API也是__stdcall调用约定。__stdcall函数调用约定将函数参数从右向左入栈,除非使用指针或引用类型的参数,所有参数采用传值方式传递,由被调用函数负责清除栈中的参数。对于C函数,__stdcall的名称修饰方式是在函数名字前添加下划线,在函数名字后添加@和函数参数的大小,例如:_functionname@number


三、三者区别


__stdcall、__cdecl和__fastcall是三种函数调用协议,函数调用协议会影响函数参数的入栈方式、栈内数据的清除方式、编译器函数名的修饰规则等。


1、调用协议常用场合


__stdcall:Windows API默认的函数调用协议。


__cdecl:C/C++默认的函数调用协议。


__fastcall:适用于对性能要求较高的场合。


2、函数参数入栈方式


__stdcall:函数参数由右向左入栈。


__cdecl:函数参数由右向左入栈。


__fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。


问题一:__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。


3、栈内数据清除方式


__stdcall:函数调用结束后由被调用函数清除栈内数据。


__cdecl:函数调用结束后由函数调用者清除栈内数据。


__fastcall:函数调用结束后由被调用函数清除栈内数据。


问题一:不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。


问题二:某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。


问题三:由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。


4、C语言编译器函数名称修饰规则


__stdcall:编译后,函数名被修饰为“_functionname@number”。


__cdecl:编译后,函数名被修饰为“_functionname”。


__fastcall:编译后,函数名给修饰为“@functionname@nmuber”。


注:“functionname”为函数名,“number”为参数字节数。


注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。


5、C++语言编译器函数名称修饰规则


__stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。


__cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。


__fastcall:编译后,函数名被修饰为“?functionname@@YI******@Z”。


注:“******”为函数返回值类型和参数类型表。


注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。

C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。


目录
打赏
0
0
0
0
40
分享
相关文章
__stdcall,__cdecl,_cdecl,_stdcall,。__fastcall,_fastcall 区别简介
1. 今天写线程函数时,发现msdn中对ThreadProc的定义有要求:DWORD WINAPI ThreadProc(LPVOID lpParameter); 不解为什么要用WINAPI宏定义,查了后发现下面的定义。
1319 0
__declspec,__cdecl,__stdcall都是什么意思?有什么作用?
__cdecl和__stdcall都是函数调用规范(还有一个__fastcall),规定了参数出入栈的顺序和方法,如果只用VC编程的话可以不用关心,但是要在C++和Pascal等其他语言通信的时候就要注意了,只有用相同的方法才能够调用成功.
1629 0
15、__stdcall,、__cdecl,thiscall等宏
1、(Microsoft Specific)__stdcall主要指明了恢复堆栈的规则:在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且负责恢复堆栈。 The __stdcall calling convention is used to call Win32 API functions.
1015 0
stdcall
读者可能会注意到上面的_stdcall这个词,它实际上是 Microsoft 。对 编译器的一个扩展,任何一个支持开发下win32应用程序的编译器都会有此 或与此等价的选项。例如. Borland ,watcom  的编译器均有些选项。用 _stacall标记的函数将使用标准的调用约定,即这些函数将在返回到调用 者之前将参数从栈中删除,Pascal函数对于栈的处理使用的也是同一种方
820 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等