_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变量的大小,即清空函数栈
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变量的大小,即清空函数栈
参考资料:
1、百度百科,_cdecl,http://baike.baidu.com/view/1280676.htm
2、代码疑云(2)-c函数调用约定,http://blog.csdn.net/zhanxinhang/article/details/6586962