函数调用约定_stdcall和_cdecl

  _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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值