调用方式__cdecl和__stdcall的异同点

我们最常用的调用约定有以下2种,__cdecl__stdcall, __cdecl c/c++的默认调用约定(calling convention), __stdcallwindows api 函数的调用约定。这2种调用约定的参数传递方式是一样的, 都是从右至左; 在堆栈的维护方式上, __cdecl要求调用者清除堆栈, __stdcall由被调用函数自己清除堆栈;名称修饰上,__cdecl直接在原有的函数名称上加一个下划线_,而__stdcall方式不仅加下划线而且还在后面加一个“@参数占用字节数,具体如何,请看下面的例子。

 

#include <stdio.h>

 

int __cdecl funcA(int a, int b);

int __stdcall funcB(int a, int b);

 

int main()

{

    int a, b, c, d;

 

    a = 3;

    b = 4;

 

    c = funcA(a, b);

    d = funcB(a, b);

 

    return 0;

}

 

int __cdecl funcA(int a, int b)

{

    return a + b;

}

 

int __stdcall funcB(int a, int b)

{

    return a + b;

}

 

 

上面2个函数funcAfuncB 一个采用__cdecl调用方式, 一个采用__stdcall调用方式,在main函数中,我们来调用这2个函数, 看看产生的汇编代码有什么不同:

 

 

11:       int a, b, c, d;

12:

13:       a = 3;

00401048   mov         dword ptr [ebp-4],3

14:       b = 4;

0040104F   mov         dword ptr [ebp-8],4

15:

16:       c = funcA(a, b); // __cdecl 调用方式

00401056   mov         eax,dword ptr [ebp-8]

00401059   push        eax

0040105A   mov         ecx,dword ptr [ebp-4]

0040105D   push        ecx

0040105E   call        @ILT+0(_funcA) (00401005)

00401063   add         esp,8 // 由调用者main平衡堆栈

00401066   mov         dword ptr [ebp-0Ch],eax

17:       d = funcB(a, b); // __stdcall调用方式

00401069   mov         edx,dword ptr [ebp-8]

0040106C   push        edx

0040106D   mov         eax,dword ptr [ebp-4]

00401070   push        eax

00401071   call        @ILT+10(_funcB@8) (0040100f)

00401076   mov         dword ptr [ebp-10h],eax

18:

19:       return 0;

00401079   xor         eax,eax

 

堆栈:

 

……

a

Ebp - 4

b

Ebp – 8

c

Ebp – 0Ch

d

Ebp - 10h

 

…..

 

由汇编代码可以看出,程序定义的四个local变量a,b,c,d分别对应于栈空间的ebp-4,

 ebp-8, ebp-0Ch, ebp-10h

程序中a=3, 这个赋值语句对应的汇编代码是

mov dword ptr[ebp-4], 3

同样,赋值语句b=4, 对应的汇编代码是

mov dword ptr[ebp-8], 4

函数funcA调用后的返回值保存在c中,

mov dword ptr[ebp-0Ch], eax

函数funcB调用后的返回值保存在d中,

mov dword ptr[ebp-10h], eax

 

 

 

现在看看__cdecl调用方式的特点:

 

从参数传递上来看:

c=funcA(a, b);

对应汇编:

mov eax, dword ptr[ebp-8]

push eax      ;压入参数b

mov ecx, dword ptr[ebp-4]

push ecx      ;压入参数a

 

从这里的汇编可以很明显的看出, 先传入参数b,然后传入参数a 即参数传递由右到左

 

从堆栈清除来看:

call        @ILT+0(_funcA) (00401005)

add         esp,8 

 

函数funcA调用之后,main函数又用了一句代码add esp, 8来恢复堆栈, __cdecl调用方式是由调用者清除堆栈的

 

从函数修饰来看:

call        @ILT+0(_funcA) (00401005)

 

从这里可以看出,原有的函数funcA被修改成了_funcA,即__cdecl调用方式是直接在原有的函数前面加一个下划线来修饰的。

 

 

接着,我们来看看__stdcall方式调用的特点:

d = funcB(a, b);

 

从参数传递来看:

mov edx,dword ptr [ebp-8]

push edx      ;传入参数b

mov eax,dword ptr [ebp-4]

push eax      ;传入参数a

 

很明显,跟__cdecl调用方式一样,参数都是从右到左的

 

从清除堆栈来看:

call        @ILT+10(_funcB@8) (0040100f)

 

函数调用之后,并没有任何清除堆栈的操作,而堆栈肯定是要保持平衡的,主函数main里面没有清除堆栈的操作,那么也就意味着堆栈的清除是由被调用函数自己做的, __stdcall调用方式,由被调用函数自己清除堆栈

 

从函数修饰来看:

call        @ILT+10(_funcB@8) (0040100f)

 

函数被修改成了_funcB@8 即修饰后的函数是原有的函数的前面加上了下划线,后面加上参数所占空间大小,中间用@隔开。因为有2个参数int a, int b,而sizeof(int)=4, 2个加起来等于8 如果是这样int funcB(int a, double 8), 则修饰后的函数是_funcB@12 因为sizeof(int)+sizeeof(double)=4+8=12

 

__cdecl调用方式和__stdcall调用方式用表格总结一下:


调用方式

参数传递

堆栈清除

函数修饰

__cdecl

从右到左

调用者清除堆栈

funcA(int a, int b); =>_funcA

__stdcall

从右到左

被调用者自己清除堆栈

funcB(int a, int b);

=> _funcB@8

 

__cdecl因为是由调用者清除堆栈的,故可以使用可变参数,最明显的一个例子就是printf函数,采用的就是__cdecl调用方式, 同时由于__cdecl是调用者清除堆栈,所以,每当调用一个函数,都会增加清除堆栈的代码,故产生的最终代码要比__stdcall方式要大,占用磁盘空间也大。

 

原为地址:http://blog.chinaunix.net/u/5391/showart_1772654.html

你可能感兴趣的:(C++,c,windows,.net,C#)