您的位置:首页 > 其它

函数调用约定__stdcall与__cdecl

2010-06-27 01:43 232 查看
在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的代码真正执行时发生改变。此时函数调用的栈框架如下图所示:



第三行和第四行才是真正完成加法运算的汇编代码。可以看到,对操作数的寻址都是通过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的不同了吧~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: