您的位置:首页 > 其它

stdcall与cdecl的区别

2016-12-14 08:49 232 查看
 
1 区别

VC++的C/C++函数有两种基本的调用约定:__stdcall、__cdecl。它们有什么区别呢?请参考下表:

  __stdcall __cdecl 
函数代码Cint __stdcall addS(int a,int b)
{
return a + b;
int __cdecl addC(int a,int b)
{
return a + b;
}
ASM32push    ebp
mov    ebp,esp
sub    esp,40h
push    ebx
push    esi
push    edi
lea    edi,[ebp-40h]
mov    ecx,10h
mov    eax,0CCCCCCCCh
rep    stos dword ptr [edi]
mov    eax,dword ptr [ebp+8]
add    eax,dword ptr [ebp+0Ch]
pop    edi
pop    esi
pop    ebx
mov    esp,ebp
pop    ebp
ret    8
push    ebp
mov    ebp,esp
sub    esp,40h
push    ebx
push    esi
push    edi
lea    edi,[ebp-40h]
mov    ecx,10h
mov    eax,0CCCCCCCCh
rep    stos dword ptr [edi]
mov    eax,dword ptr [ebp+8]
add    eax,dword ptr [ebp+0Ch]
pop    edi
pop    esi
pop    ebx
mov    esp,ebp
pop    ebp
ret
函数调用CaddS(1,2); addC(1,2);
ASM32push    2
push    1
call    @ILT+85(addS)
push    2
push    1
call    @ILT+50(addC)
add    esp,8
说明:

1、函数 addS、addC 的调用约定分别是__stdcall、__cdecl,它们32位汇编代码的不同之处仅仅在于ret 8和ret;

2、addS(1,2)与addC(1,2)的32位汇编代码不同之处在于后者多了一个add esp,8;

3、寄存器esp保存着程序堆栈的栈顶地址

调用addS(1,2)的过程是这样的:push 2、push 1用来把参数压入堆栈,压入了两个4字节的int,所以esp的数值会减去8。然后call语句调用addS的汇编代码。形参a的数值是dword ptr [ebp+8],即push 1压入的1;形参b的数值是dword ptr [ebp+0Ch],即push 2压入的2。ebp其实是esp的初始值(mov ebp,esp),所以参数a、b是取自堆栈的。addS返回时调用的是ret 8,亦即返回时会将寄存器esp的数值加上8,完成堆栈的清除工作。

调用addC(1,2)的过程与addS(1,2)大致相同,不同之处在于ret不会修改寄存器esp,不会完成堆栈的清除工作。在执行add esp,8时,才会将esp寄存器的数值加上8,完成堆栈的清除工作。

简单的说,就是:__stdcall的函数在返回时会自动清除堆栈中的参数;__cdecl的函数在返回时不会自动清除堆栈中的参数,清除工作由调用者完成。

2 函数指针

再看看下面的例子:

函数调用int(__stdcall*pfnS)(int,int)=&addS;
(*pfnS)(1,2);
int(__cdecl*pfnC)(int,int)=&addC;
(*pfnC)(1,2);
ASM32mov    dword ptr [ebp-14h],offset @ILT+85(addS) (0040105a)
mov    esi,esp
push    2
push    1
call    dword ptr [ebp-14h]

 cmp    esi,esp
call    _chkesp (00401f1e)
mov    dword ptr [ebp-18h],offset @ILT+50(addC) (00401037)
mov    esi,esp
push    2
push    1
call    dword ptr [ebp-18h]
add    esp,8
cmp    esi,esp
call    _chkesp (00401f1e)
调用(*pfnS)(1,2)比调用addS(1,2)多了如下代码:

mov    esi,esp            //保存寄存器esp的数值至寄存器esi
... ... ...
cmp    esi,esp            //查看寄存器esp的数值是否变化了
call    _chkesp (00401f1e)     //寄存器esp的数值变化了,提示出错
也就是说:通过函数指针调用函数,会检查寄存器esp的数值是否被正常恢复。

如果强制 pfnS 指向 addC,然后执行(*pfnS)(1,2);,会发生什么?请参考如下代码:

int(__stdcall*pfnS)(int,int)=(int(__stdcall*)(int,int))&addC;
(*pfnS)(1,2);
运行时会产生如下错误提示,说明寄存器 ESP 产生了错误:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: