_stdcall,_cdecl与extern "C"
调用一个函数时,总是先把参数压入栈,然后通过call指令转移到被调用函数,在完成调用后清除堆栈.这里有两个问题:(1)哪个参数先入栈(2)由谁来清理堆栈.这两个方面的问题称为"调用约定(Calling Conventions)"问题.
这里只讨论_stdcall和_cdecl调用约定,前者是Windows API函数常用的调用约定,后者即是C调用约定.
_stcall:参数按从右向左的顺序入栈,由被调用函数清理堆栈.
_cdecl :参数按从右向左的顺序入栈,由调用函数清理堆栈.
为了防止函数名冲突,或者重载的需要(c++程序),编译器通常都会修改用户定义的函数名.
这样说也许太抽象了,还是来看看具体的例子吧(通常代码最能说明问题,也最能把问题说清楚).
来看一段简单的代码:
很简单,即实现两个数相交换,学过c语言的人对这段代码应该很熟悉.
再来加一段测试代码:
以下的测试基于VC++6.0.
(1)swap函数不加任何修饰(最简单的情况)
来看看它们相应的汇编代码:
//main调用swap的汇编代码
swap(&a,&b);
00401096 8D 45 F8 lea eax,[ebp-8]
00401099 50 push eax
0040109A 8D 4D FC lea ecx,[ebp-4]
0040109D 51 push ecx
0040109E E8 62 FF FF FF call @ILT+0(swap) (00401005)
004010A3 83 C4 08 add esp,8
//swap的汇编代码
void swap(int *x,int *y)
{
00401020 55 push ebp
00401021 8B EC mov ebp,esp
00401023 83 EC 44 sub esp,44h
00401026 53 push ebx
00401027 56 push esi
00401028 57 push edi
00401029 8D 7D BC lea edi,[ebp-44h]
0040102C B9 11 00 00 00 mov ecx,11h
00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401036 F3 AB rep stos dword ptr [edi]
int temp;
temp=*x;
00401038 8B 45 08 mov eax,dword ptr [ebp+8]
0040103B 8B 08 mov ecx,dword ptr [eax]
0040103D 89 4D FC mov dword ptr [ebp-4],ecx
*x=*y;
00401040 8B 55 08 mov edx,dword ptr [ebp+8]
00401043 8B 45 0C mov eax,dword ptr [ebp+0Ch]
00401046 8B 08 mov ecx,dword ptr [eax]
00401048 89 0A mov dword ptr [edx],ecx
*y=temp;
0040104A 8B 55 0C mov edx,dword ptr [ebp+0Ch]
0040104D 8B 45 FC mov eax,dword ptr [ebp-4]
00401050 89 02 mov dword ptr [edx],eax
}
00401052 5F pop edi
00401053 5E pop esi
00401054 5B pop ebx
00401055 8B E5 mov esp,ebp
00401057 5D pop ebp
00401058 C3 ret
@ILT+0(?swap@@YAXPAH0@Z):
00401005 E9 16 00 00 00 jmp swap (00401020)
swap被编译器修改为 :?swap@@YAXPAH0@Z,这是c++转换方式.
(2)_stdcall
swap(&a,&b);
004010E6 8D 45 F8 lea eax,[ebp-8]
004010E9 50 push eax
004010EA 8D 4D FC lea ecx,[ebp-4]
004010ED 51 push ecx
004010EE E8 12 FF FF FF call @ILT+0(swap) (00401005)
void _stdcall swap(int *x,int *y)
{
00401030 55 push ebp
00401031 8B EC mov ebp,esp
00401033 83 EC 44 sub esp,44h
00401036 53 push ebx
00401037 56 push esi
00401038 57 push edi
00401039 8D 7D BC lea edi,[ebp-44h]
0040103C B9 11 00 00 00 mov ecx,11h
00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401046 F3 AB rep stos dword ptr [edi]
int temp;
temp=*x;
00401048 8B 45 08 mov eax,dword ptr [ebp+8]
0040104B 8B 08 mov ecx,dword ptr [eax]
0040104D 89 4D FC mov dword ptr [ebp-4],ecx
*x=*y;
00401050 8B 55 08 mov edx,dword ptr [ebp+8]
00401053 8B 45 0C mov eax,dword ptr [ebp+0Ch]
00401056 8B 08 mov ecx,dword ptr [eax]
00401058 89 0A mov dword ptr [edx],ecx
*y=temp;
0040105A 8B 55 0C mov edx,dword ptr [ebp+0Ch]
0040105D 8B 45 FC mov eax,dword ptr [ebp-4]
00401060 89 02 mov dword ptr [edx],eax
}
00401062 5F pop edi
00401063 5E pop esi
00401064 5B pop ebx
00401065 8B E5 mov esp,ebp
00401067 5D pop ebp
00401068 C2 08 00 ret 8
@ILT+0(?swap@@YGXPAH0@Z):
00401005 E9 16 00 00 00 jmp swap (00401020)
(3)_cdecl
void _cdecl swap(int *x,int *y)
与第一种情况相同,即这是vc++默认的方式.
(4)extern "C" _cdecl
void extern "C" _cdecl swap(int *x,int *y)
swap(&a,&b);
00401096 8D 45 F8 lea eax,[ebp-8]
00401099 50 push eax
0040109A 8D 4D FC lea ecx,[ebp-4]
0040109D 51 push ecx
0040109E E8 67 FF FF FF call @ILT+5(_swap) (0040100a)
004010A3 83 C4 08 add esp,8
@ILT+5(_swap):
0040100A E9 11 00 00 00 jmp swap (00401020)
swap被编译器修改为_swap,即加一个下划线(c语言转换方式),其余与第三种情况相同.
(5)extern "C" _stdcall
void extern "C" _stdcall swap(int *x,int *y)
swap(&a,&b);
00401096 8D 45 F8 lea eax,[ebp-8]
00401099 50 push eax
0040109A 8D 4D FC lea ecx,[ebp-4]
0040109D 51 push ecx
0040109E E8 62 FF FF FF call @ILT+0(_swap@8) (00401005)
swap被修改为_swap@8,即加一个下划线,后面加上参数在堆栈中所占的字节数(c语言转换方式),其余与第二种情况相同.