当前位置:首页 > 资讯 > info5 > 正文

函数调用约定__stdcall与__cdecl

发表于: 2010-06-27   作者:FlyingIceCS   来源:转载   浏览次数:
摘要: 在Windows编程中,我们经常看到如intWINAPI_tWinMain(HINSTANCEhInstanceExe,PSTRpszCmdLine,intnCmdShow)这样的函数定义,它被WINAPI所修饰。WINAPI其实是一个宏,我们可以在WinDef.h中找到它的定义:#defineWINAPI     __stdcall__stdcall是函数调用约定。所谓函数调用约定,其实是主调和

在Windows编程中,我们经常看到如int WINAPI _tWinMain(HINSTANCE hInstanceExe, PSTR pszCmdLine, int nCmdShow)这样的函数定义,它被WINAPI所修饰。WINAPI其实是一个宏,我们可以在WinDef.h中找到它的定义:

#define WINAPI      __stdcall

__stdcall是函数调用约定。所谓函数调用约定,其实是主调和被调函数间的协议,该协议事先约定好函数参数以什么顺序依次压栈,以及函数调用结束后由谁来完成对入栈参数的清理。__stdcall和__cdecl是其中两个最常用的调用约定,其中__stdcall是PASCAL默认的调用方式,而__cdecl则为C/C++所默认。在使用VC进行编程时,我们可以在工程属性--C/C++--Advanced--Calling Convention中对该编译选项进行改变和设置。

如果我们定义一个C函数f并调用它:

int f(int x, int y) { return x + y; } int g = f(a, b);

我们使用__stdcall调用约定,编译器编译后产生如下汇编代码,对于主调函数:

push b push a call f() mov g, eax

可以看到,函数f的参数是按照从右到左的顺序依次压栈的,__stdcall和__cdecl两种调用约定在参数压栈顺序上是一致的。call汇编指令使eip跳转,并且它包含了将函数返回地址压栈的操作。

相应地,对于被调函数:

push ebp mov ebp, esp mov eax, [ebp + 0x08] add eax, [ebp + 0x0C] mov esp, ebp pop ebp ret 8

被调函数f开始执行前首先将ebp压栈,这是为了在f调用结束后重建主调函数栈框架。mov ebp, esp使得ebp指向当前函数(即被调函数f)的栈顶。因为对于被调函数f来说,它的栈目前是空的,所以栈底指针和栈顶指针相同。函数f内部需要为局部变量分配空间时,esp则会向下移动使得总是指向栈顶,而ebp不会在f的代码真正执行时发生改变。此时函数调用的栈框架如下图所示:

函数调用约定__stdcall与__cdecl_第1张图片

第三行和第四行才是真正完成加法运算的汇编代码。可以看到,对操作数的寻址都是通过ebp进行的。事实上,ebp向下的栈空间保存的都是当前函数的局部变量,而ebp向上则保存了当前函数的返回地址和压栈参数。在32位机器上指针占用4个字节内存空间,ebp + 0x08依次跳过第一行代码压栈的ebp值(4字节)和函数返回地址值(4字节),因而取出的就是a的值,a作为int型参数本身占用4字节,故ebp + 0x0C处存放的是另一个操作数b的值。

加法操作完成后,esp的值被ebp覆盖,也就是将当前函数的栈完全清空,而函数栈清空就意味着函数f的局部变量全部被销毁了(简单起见本例函数f中没有声明局部变量)。pop操作将函数f开始执行前压栈保存的ebp弹出,于是ebp重新回到了主调函数的栈框架中,esp指向了函数f的返回地址。ret使eip跳转至该返回地址,除此之外,它还做了一件很关键的事情--将esp向上移动8个字节,这8个字节中存储的正是压栈的参数a和b。这表明,__stdcall是由被调用者来负责清理栈参数的。

对于相同的C函数定义函数f,我们再来看看采用__cdecl时产生的汇编代码。对于主调函数:

push b push a call f() add esp, 8 mov g, eax

对于被调函数:

push ebp mov ebp, esp mov eax, [ebp + 0x08] mov eax, [ebp + 0x0C] mov esp, ebp pop ebp ret

看到不同了吗?主调函数多出来一条add esp, 8,这正是清理压栈参数的操作,而这个操作现在放在主调函数中执行了。被调函数ret少了操作数8,它现在只负责使eip跳转,不再有移动esp的操作。另外一方面,使用__cdecl时函数参数压栈顺序也是从右至左,这一点上文提到过。__cdecl是C/C++的默认调用约定,正是因为如此,C语言才能很好地支持不定参数声明。由于VC新建工程时默认的是__cdecl,所以想采用__stdcall时一定要在函数前显式指明。另外,WinDef.h头文件中还提供了#define CALLBACK    __stdcall 的定义,这里的callback就是我们常常听到的回调函数。

现在知道了__stdcall与__cdecl的不同了吧~

函数调用约定__stdcall与__cdecl

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
  _cdecl 是C Declaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些
1. __cdecl __cdecl 是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:
函数调用约定和堆栈 1 什么是堆栈 编译器一般使用堆栈实现函数调用。堆栈是存储器的一个区域,嵌入
1 什么是堆栈 编译器一般使用堆栈实现函数调用。堆栈是存储器的一个区域,嵌入式环境有时需要程序员
JIT脚本引擎:stdcall、cdecl和fastcall stdcall、cdecl和fastcall的参数都是从右到左入栈,并且返
最近在写一些字符串函数的优化,用到x64汇编,我也是第一次接触,故跟大家分享一下。 x86:又名 x32
小议三种函数调用约定 __cdecl、__stdcall、__fastcall是C/C++里中经常见到的三种函数调用方式。其
1.示例 栈的作用和功能这里就不再叙述了。 先看一个最简单的函数调用例子: #include "stdafx.h" in
http://zh.wikipedia.org/wiki/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A 这里描述了在x86芯片架构
int bar( int a, int b, int c, int d, int e, int f, int g ) { int array2[ 7 ]; array2[ 0 ] = a
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号