__stdcall与__cdecl函数调用的不同

参考:

http://blog.csdn.net/hudashi/article/details/7820338

http://shitou7630.blog.163.com/blog/static/32699536201342110155436/

http://www.cnblogs.com/52yixin/archive/2011/06/29/2093634.html

http://blog.csdn.net/mniwc/article/details/7993361

http://www.cnblogs.com/coderzh/archive/2008/12/01/1345053.html

http://blog.sina.com.cn/s/blog_6f6769b50100uhzz.html

https://msdn.microsoft.com/zh-cn/library/ms235286.aspx

http://blog.csdn.net/pizi0475/article/details/5312357

 

(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu 转载请标明来源)

 

ESP是栈顶指针   Extended Stack Pointer

EBP是基址指针   Extend Base Pointer

ESP就是一直指向栈顶的指针,EBP只是用于存取某时刻的栈顶指针,以方便对栈的操作

 

 

l  (win32下) 使用__stdcall的函数定义和函数翻译

void __stdcall TestCall(int a, int b)

{

      return;

}

 

执行

TestCall(1, 2);

 

使用__stdcall的函数翻译:  (执行时转到反汇编窗口)

TestCall(1, 2);

00F013FE  push        2   

00F01400  push        1   

00F01402  call        TestCall (0F01177h)

 

void __stdcall TestCall(int a, int b)

{

      return;

}

00F013B0  push        ebp         //压栈

00F013B1  mov         ebp,esp     //保存esp到ebp

00F013B3  sub         esp,0C0h

00F013B9  push        ebx 

00F013BA  push        esi 

00F013BB  push        edi 

00F013BC  lea         edi,[ebp-0C0h]

00F013C2  mov         ecx,30h

00F013C7  mov         eax,0CCCCCCCCh

00F013CC  rep stos    dword ptr es:[edi]

00F013CE  pop         edi 

00F013CF  pop         esi 

00F013D0  pop         ebx 

00F013D1  mov         esp,ebp     //从ebp还原esp

00F013D3  pop         ebp         //出栈还原ebp

00F013D4  ret         8           //返回并退栈8字节,效果等同 add esp, 8; ret;

(注意,压栈时esp地址变小,出栈时esp地址变大,因为是从后往前分配的。)

 

l  (win32下) 使用__cdecl或缺省的函数定义和函数翻译

 

void __stdcall TestCall(int a, int b)

{

      return;

}

 

执行

TestCall(1, 2);

 

使用__cdecl或缺省的函数翻译:  (执行时转到反汇编窗口)

 

TestCall(1, 2);

012B13FE  push        2   

012B1400  push        1   

012B1402  call        TestCall (12B11D6h)

012B1407  add         esp,8       // esp退栈8字节,把push 2; push 1;压入的8字节退掉。

 

 

void TestCall(int a, int b)

{

      return;

}

00F013B0  push        ebp         //压栈

00F013B1  mov         ebp,esp     //保存esp到ebp

00F013B3  sub         esp,0C0h

00F013B9  push        ebx 

00F013BA  push        esi 

00F013BB  push        edi 

00F013BC  lea         edi,[ebp-0C0h]

00F013C2  mov         ecx,30h

00F013C7  mov         eax,0CCCCCCCCh

00F013CC  rep stos    dword ptr es:[edi]

00F013CE  pop         edi 

00F013CF  pop         esi 

00F013D0  pop         ebx 

00F013D1  mov         esp,ebp     //从ebp还原esp

00F013D3  pop         ebp         //出栈还原ebp

00F013D4  ret                     //直接返回,不进行退栈操作

 

 (win32下)两者比较

 

从上面可以看出

使用__stdcall的话,调用侧从右往左压栈函数参数,但不退栈,需要函数退栈

(ret 8  — esp退栈8个字节)

 

使用__cdecl的话,调用侧从右往左压栈函数参数,并且在调用后,调用侧主动执行退栈 (add esp, 8  —  esp退栈8个字节)

 

l  不用退栈的情况

 

有一些情况,由于不用退栈,调用他人声明的函数时用哪种都不会报错

 

无参数的情况:

这种情况,由于调用侧未压栈函数参数,所以不存在函数参数的退栈问题,退栈也只需退0长度

 

winX64位情况下编译,当参数少于4个时,未进行压栈:

参考:

https://msdn.microsoft.com/zh-cn/library/ms235286.aspx

http://blog.sina.com.cn/s/blog_6f6769b50100uhzz.html

http://openwares.net/misc/windows_x64_function_call_convention.html

前四个参数被放入到寄存器中,而不是压入堆栈中的,同无参数一样,也不存在退栈情况。

(通常参数在寄存器 RCXRDXR8 R9中传递。)

例如

void __stdcall TestCall(int& a, int& b)

{

      return;

}

int a = 1;

int b = 2;

TestCall(a, b);

 

      TestCall(a, b);

000000013F031247  mov         edx,dword ptr [b]

000000013F03124B  mov         ecx,dword ptr [a]

000000013F03124F  call        TestCall (13F031028h)

 

void __stdcall TestCall(int& a, int& b)

{     return;

}

000000013F0311B0  mov         dword ptr [rsp+10h],edx

000000013F0311B4  mov         dword ptr [rsp+8],ecx

000000013F0311B8  push        rdi 

000000013F0311B9  pop         rdi 

000000013F0311BA  ret        

 

Gcc编译时,更加不同,这里不作详细介绍:

参考 http://blog.sina.com.cn/s/blog_6f6769b50100uhzz.html

“win_hate”: 发现一般规则为,当参数6个及6个以内时,参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。当参数为 6个以上时,前 6 个与前面一样,但后面的依次从右向左放入栈中。

 

 

l  其它说明

 

参考文档中对这两种调用的说明:

http://www.cnblogs.com/coderzh/archive/2008/12/01/1345053.html

1_stdcallPascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上“@”和参数的字节数。 int f(void *p) –>> [email protected](在外部汇编语言里可以用这个名字引用这个函数)

2C调用约定(即用__cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数 vararg的函数(printf)只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 _cdeclCC++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。

 

参考: http://blog.csdn.net/pizi0475/article/details/5312357

关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting…/C/C++ /Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。
 

 

(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu 转载请标明来源)

 

点赞