您的位置:首页 > 其它

函数调用约定_stdcall和_cdecl

2012-06-25 18:55 344 查看
  _cdecl 是C Declaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

  _stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retnX,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。

#include<iostream>
using namespace std;
void foo(int p1,int p2,int p3)
{
cout<<"p1="<<p1<<endl
<<"p2="<<p2<<endl
<<"p3="<<p3<<endl;
}
int main()
{
int i;
cout<<"first call:"<<endl;
i=0;
foo(++i,++i,++i);

cout<<"second call:"<<endl;
i=0;
foo(++i,i++,i++);

cout<<"third call:"<<endl;
i=0;
foo(++i,i, i++);

cout<<"fourth call:"<<endl;
i=0;
foo(++i,i++,++i);

system("PAUSE");
return 0;
}
结果为:



我这里是C++代码,函数调用的方式应该是 _stdcall(默认的)。用void _cdecl
foo(int p1,int p2,int p3)和void
_stdcall foo(int p1,int p2,int p3)的结果都是一样的,如上图所示。_stdcall 和 _cdecl函数调用都是参数从右到左依次压栈。但在参数里面出现++i和i++时,有几点需要说明的是:

1、有++i和i++时,先计算++操作,再进行压栈操作。

2、++i返回的i的引用(加1)相当于{i = i + 1; return &i},i++返回的是一个临时变量(没有加1)相当于{int temp = i; i = i + 1;return temp}。压栈的时候++i使用的i本身的值,i++压栈使用的这个临时变量值temp。

下面是visual studio 2008的反汇编代

foo(++i,++i,++i)的反汇编代码:

i=0;
00411669  mov         dword ptr [i],0     // 初始化i的内存单元的值
foo(++i,++i,++i);
00411670  mov         eax,dword ptr [i]   // 把i放到eax寄存器
00411673  add         eax,1               // 把eax的值加1
00411676  mov         dword ptr [i],eax	  // 用eax值修改i的内存单元
00411679  mov         ecx,dword ptr [i]
0041167C  add         ecx,1
0041167F  mov         dword ptr [i],ecx
00411682  mov         edx,dword ptr [i]
00411685  add         edx,1
00411688  mov         dword ptr [i],edx
0041168B  mov         eax,dword ptr [i]	  // 因为++i返回的是i的引用,直接用i的内存单元的值压栈
0041168E  push        eax
0041168F  mov         ecx,dword ptr [i]   // 因为++i返回的是i的引用,直接用i的内存单元的值压栈
00411692  push        ecx
00411693  mov         edx,dword ptr [i]   // 因为++i返回的是i的引用,直接用i的内存单元的值压栈
00411696  push        edx
00411697  call        foo (411096h)       // 调用foo函数
0041169C  add         esp,0Ch             // 弹栈,3个int变量的大小,即清空函数栈


foo(++i,i++,i++)的反汇编代码

i=0;
004116CA  mov         dword ptr [i],0
foo(++i,i++,i++);
004116D1  mov         eax,dword ptr [i]
004116D4  mov         dword ptr [ebp-0D0h],eax  // ebp是栈底指针,[ebp-0D0h](++i使用的临时变量地址)的值是0
004116DA  mov         ecx,dword ptr [i]
004116DD  add         ecx,1
004116E0  mov         dword ptr [i],ecx         // 先计算i的值,从左到右,修改i的值,i==1
004116E3  mov         edx,dword ptr [i]
004116E6  mov         dword ptr [ebp-0D4h],edx  // [ebp-0D4h]的值为1
004116EC  mov         eax,dword ptr [i]
004116EF  add         eax,1
004116F2  mov         dword ptr [i],eax         // 修改i的值,i==2
004116F5  mov         ecx,dword ptr [i]
004116F8  add         ecx,1
004116FB  mov         dword ptr [i],ecx         // 修改i的值,i==3
004116FE  mov         edx,dword ptr [ebp-0D0h]  // 压栈的是i++的临时变量,顺序是从右到左
00411704  push        edx
00411705  mov         eax,dword ptr [ebp-0D4h]  // 压栈的是i++的临时变量
0041170B  push        eax
0041170C  mov         ecx,dword ptr [i]         // 压栈的是i++的临时变量
0041170F  push        ecx
00411710  call        foo (411096h)
00411715  add         esp,0Ch			 // 弹栈,3个int变量的大小,即清空函数栈


自习看上面的两段汇编代码,就可以明白函数调用是参数的压栈顺序,特别是从第二段汇编代码可以看出,函数压栈顺序是从有到左,先i++,i++,在++i。在压栈之前就已经计算好了各自的值。

参考资料:

1、百度百科,_cdecl,http://baike.baidu.com/view/1280676.htm

2、代码疑云(2)-c函数调用约定,http://blog.csdn.net/zhanxinhang/article/details/6586962
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  汇编 system c 百度 语言