周宁ID:znnren
10366次访问,排名10427(-1)好友0人,关注者0
生活随性、爱好广泛、不善与人争执
znnren的文章
原创 13 篇
翻译 0 篇
转载 8 篇
评论 14 篇
fantasyer的公告
最近评论
fallhunter:我也呆过唐图,更喜欢北洋这个名字。
唐图有很多好的地方....
不过你星际输给mm还真丢人,虽然我也打不过
fallhunter:"游戏开发依然是我的梦想之一"

在这个没有梦想的社会发现一个依然还有梦想的人
znnren:有一个关于澳洲内陆的野外探险,可以欣赏到阿雅斯岩、澳洲内陆野花之旅、土著艺术、绘画与雕刻、土著音乐等。有时间去看看土著人的生活,应该很有意思吧~
静静:我对《一生要去的50个地方》,《天才的5种创意方程式》,《生活健康密码》挺感兴趣。如果有时间能不能告诉我这50个地方又没有澳洲的,抽空去看看。还有天才的5种创意方式,如果不长的话。
图书城:买书就上图书城(www.TuShuCheng.com)
百万图书全部5到8折,送货到家再付款!
文章分类
收藏
相册
图形学
Azure
puzzy3d
云风
我的兄弟姐妹
fallhunter(RSS)
newhappy
wolfsecond(RSS)
上铺的兄弟
净空亮星
朱珊
软件第一帅哥
静静
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

转载 [ZZ]剖析VC++函数调用约定收藏

新一篇: 曾经T3上的最好战绩 | 旧一篇: 3D迷宫(DirextX9)

  原文链接:http://blog.csdn.net/goodname008/archive/2004/07/24/50662.aspx

  原文作者:卢培培(goodname008)  

Visual C/C++ 的编译器提供了几种函数调用约定,了解这些函数调用约定的含义及它们之间的区别可以帮助我们更好地调试程序。在这篇文章里,我就和大家共同探讨一些关于函数调用约定的内容。
     Visual C/C++ 的编译器支持如下的函数调用约定:
 
关键字
清理堆栈
参数入栈顺序
函数名称修饰(C)
__cdecl
调用函数
à
_函数名
__stdcall
被调用函数
à
_函数名@数字
__fastcall
被调用函数
à
@函数名@数字
thiscall(非关键字)
被调用函数
à
/
 
     上面这张表只简单地列出了每种函数调用约定的特点,既然这篇文章题目的前两个字是“剖析”,哪能这么容易就完事!?下面就对上面这四种函数调用约定逐个“剖析”:
     一、__cdecl函数调用约定
     这是C和C++ 程序默认的函数调用约定,参数按从右到左的顺序压入堆栈,由调用函数负责清理堆栈,把参数弹出栈。也正是因为用来传送参数的堆栈是由调用函数维护的,所以实现可变参数的函数只能使用这种函数调用约定。因为每一个调用它的函数都要包含清理堆栈的代码,所以编译后的可执行文件的大小要比调用__stdcall函数的大。使用这种函数调用约定时,修饰后的函数名只是在原函数名前加上了一个_(下划线),并且不改变函数的大小写。对于__cdecl,我们一般不特别指出,因为它是C和C++ 程序默认的函数调用约定,所以只有将编译选项设置成/Gz(stdcall)或/Gr(fastcall)时,我们才有必要在函数名前显式地指出采用这种函数调用约定。下面举一个例子:
 
int __cdecl Sumcdecl(int a, int b, int c)
{
int i = 1000;
short j = 2000;
int k = 3000;
int rEBP = 0;
int value = 0;
 
// ...
 
return (a + b + c);
 
}
 
调用:Sumcdecl(10, 20, 30);
 
 
函数体及调用语句如上所示,修饰后的函数名为_Sumcdecl,堆栈和寄存器状态如下(一行表示4个字节):
 
0
 value
0
 rEBP
3000
 k
2000
 j
1000
 i
 
 <---------EBP
 
 
10
 a
20
 b
30
 c
 
 
[未使用]
 ECX
[未使用]
 EDX
 
     口说无凭,代码能说明一切,下面的程序乃Win32 console application(.exe)是也:
 
#include "iostream.h"
#include "stdio.h"
 
extern "C" __declspec(dllexport) int __cdecl Sumcdecl(int a, int b, int c)
{
// 声明局部变量
     int i = 1000;
     short j = 2000;
     int k = 3000;
     int rEBP = 0;
     int value = 0;
 
     // 显示局部变量的地址
     cout << "局部变量的地址:" << endl;
     cout << &value << "    <-----------value" << endl;
     cout << &rEBP << "    <-----------rEBP" << endl;
     cout << &k << "    <-----------k" << endl;
     cout << &j << "    <-----------j" << endl;
     cout << &i << "    <-----------i" << endl;
 
     // 显示寄存器的值
     cout << "寄存器:" << endl;
     __asm mov rEBP, ebp;
     printf("0x%08X    <-----------EBP\n", rEBP);
 
     // 显示函数参数的地址
     cout << "函数参数的地址:" << endl;
     cout << &a << "    <-----------a" << endl;
     cout << &b << "    <-----------b" << endl;
     cout << &c << "    <-----------c" << endl;
 
     // 通过 EBP 寄存器获得堆栈中的数据并显示
     cout << "通过EBP获取堆栈中的数据:" << endl;
     __asm mov eax, [ebp - 4];
     __asm mov value, eax;
     cout << "i: " << value << endl;
 
     __asm mov eax, [ebp - 8];
     __asm mov value, eax;
     cout << "j: " << (short)value << endl;
 
     __asm mov eax, [ebp - 12];
     __asm mov value, eax;
     cout << "k: " << value << endl;
 
     __asm mov eax, [ebp + 8];
     __asm mov value, eax;
     cout << "a: " << value << endl;
 
     __asm mov eax, [ebp + 12];
     __asm mov value, eax;
     cout << "b: " << value << endl;
 
     __asm mov eax, [ebp + 16];
     __asm mov value, eax;
     cout << "c: " << value << endl;
 
     // 返回
     return (a + b + c);
 
}
 
// 主函数
int main(int argc, char* argv[])
{
 
     Sumcdecl(10, 20, 30);
 
     return 0;
 
}
 
 
     在我的机器上,运行结果如下:
 
局部变量的地址:
0x0012FF0C    <-----------value
0x0012FF10    <-----------rEBP
0x0012FF14    <-----------k
0x0012FF18    <-----------j
0x0012FF1C    <-----------i
寄存器:
0x0012FF20    <-----------EBP
函数参数的地址:
0x0012FF28    <-----------a
0x0012FF2C    <-----------b
0x0012FF30    <-----------c
通过EBP获取堆栈中的数据:
i: 1000
j: 2000
k: 3000
a: 10
b: 20
c: 30
 
     函数声明部分的extern “C”表示连接规范(Linkage Specification)采用C,而不是C++,不加extern “C”的情况我会在后面统一讨论。__declspec(dllexport)表示将该函数导出,将生成.lib文件,以便我们验证函数名是怎样修饰的。关于修饰后的函数名,我们可以使用VC98\bin目录下的dumpbin工具来验证:
 
     dumpbin /exports 文件名>
 
     输出结果如下:
 
File Type: LIBRARY
 
     Exports
 
       ordinal    name
 
                  _Sumcdecl
 
 Summary
 
          C9 .debug$S
          14 .idata$2
          14 .idata$3
           4 .idata$4
           4 .idata$5
           E .idata$6
 
     二、__stdcall函数调用约定
     __stdcall函数调用约定通常用于Win32 API函数,参数按从右到左的顺序压入堆栈,由被调用函数负责清理堆栈,把参数弹出栈。在windows.h中包含了windef.h,而windef.h中定义了一个WINAPI宏:#define WINAPI __stdcall,呵呵,应该心知肚明了。使用这种函数调用约定时,修饰后的函数名在原函数名前加上了一个_(下划线),并且在原函数名后加上“@数字”,当然也不改变函数的大小写,@ 后面的数字表示参数所占的字节数,这里有一点要注意的,不足32位(4字节)的参数将在参数传递时被扩充到32位。下面举一个例子:
 
int __stdcall Sumstdcall(int a, int b, int c)
{
int i = 1000;
short j = 2000;
int k = 3000;
int rEBP = 0;
int value = 0;
 
// ...
 
return (a + b + c);
 
}
 
调用:Sumstdcall(10, 20, 30);
 
 
     函数体及调用语句如上所示,修饰后的函数名为_Sumstdcall@12,int是32位的,占4个字节,3个32位的变量,共12个字节。堆栈和寄存器状态如下(一行表示4个字节):
 
0
 value
0
 rEBP
3000
 k
2000
 j
1000
 i
 
 <---------EBP
 
 
10
 a
20
 b
30
 c
 
 
[未使用]
 ECX
[未使用]
 EDX
 
 
     仍然以代码说明:
 
#include "iostream.h"
#include "stdio.h"
 
extern "C" __declspec(dllexport) int __stdcall Sumstdcall(int a, int b, int c)
{
     // 声明局部变量
     int i = 1000;
     short j = 2000;
     int k = 3000;
     int rEBP = 0;
     int value = 0;
 
     // 显示局部变量的地址
     cout << "局部变量的地址:" << endl;
     cout << &value << "    <-----------value" << endl;
     cout << &rEBP << "    <-----------rEBP" << endl;
     cout << &k << "    <-----------k" << endl;
     cout << &j << "    <-----------j" << endl;
     cout << &i << "    <-----------i" << endl;
 
     // 显示寄存器的值
     cout << "寄存器:" << endl;
     __asm mov rEBP, ebp;
     printf("0x%08X    <-----------EBP\n", rEBP);
 
     // 显示函数参数的地址
     cout << "函数参数的地址:" << endl;
     cout << &a << "    <-----------a" << endl;
     cout << &b << "    <-----------b" << endl;
     cout << &c << "    <-----------c" << endl;
 
     // 通过 EBP 寄存器获得堆栈中的数据并显示
     cout << "通过EBP获取堆栈中的数据:" << endl;
     __asm mov eax, [ebp - 4];
     __asm mov value, eax;
     cout << "i: " << value << endl;
 
     __asm mov eax, [ebp - 8];
     __asm mov value, eax;
     cout << "j: " << (short)value << endl;
 
     __asm mov eax, [ebp - 12];
     __asm mov value, eax;
     cout << "k: " << value << endl;
 
     __asm mov eax, [ebp + 8];
     __asm mov value, eax;
     cout << "a: " << value << endl;
 
     __asm mov eax, [ebp + 12];
     __asm mov value, eax;
     cout << "b: " << value << endl;
 
     __asm mov eax, [ebp + 16];
     __asm mov value, eax;
     cout << "c: " << value << endl;
 
     // 返回
     return (a + b + c);
    
}
 
// 主函数
int main(int argc, char* argv[])
{
 
     Sumstdcall(10, 20, 30);
 
     return 0;
 
}
 
 
在我的机器上,运行结果如下:
 
局部变量的地址:
0x0012FF0C    <-----------value
0x0012FF10    <-----------rEBP
0x0012FF14    <-----------k
0x0012FF18    <-----------j
0x0012FF1C    <-----------i
寄存器:
0x0012FF20    <-----------EBP
函数参数的地址:
0x0012FF28    <-----------a
0x0012FF2C    <-----------b
0x0012FF30    <-----------c
通过EBP获取堆栈中的数据:
i: 1000
j: 2000
k: 3000
a: 10
b: 20
c: 30
 
     其实和__cdecl的差不多,只是把__cdecl改成了__stdcall,又换了个函数名。用dumpbin分析.lib文件的结果如下:
 
File Type: LIBRARY
 
     Exports
 
       ordinal    name
 
                  _Sumstdcall@12
 
 Summary
 
          C9 .debug$S
          14 .idata$2
          14 .idata$3
           4 .idata$4
           4 .idata$5
           E .idata$6
 
     三、__fastcall函数调用约定
     __fastcall,顾名思义,特点就是快,因为它是靠寄存器来传递参数的。传递参数时,最左边的两个小于等于32位(4字节)的参数将被分别存入ECX和EDX寄存器,其余参数仍然按从右到左的顺序压入堆栈,由被调用函数负责清理堆栈,把参数弹出栈。这里有一点想强调一下:存入寄存器的那两个参数实际也存入到了堆栈中,后面的例子和代码将证明这一点。使用这种函数调用约定时,修饰后的函数名在原函数名前加上了一个 @,并且在原函数名后加上“@数字”,同样不改变函数的大小写,@ 后面的数字表示参数所占的字节数,其实和__stdcall差不多,只是把最前面的_(下划线)换成了@。下面举一个例子,和前面两个稍有不同:
 
int __fastcall Sumfastcall(int a, double x, int b, int c)
{
     int i = 1000;
     short j = 2000;
     int k = 3000;
     int rEBP = 0;
     int rECX = 0;
     int rEDX = 0;
     int value = 0;
 
// ...
 
return (a + b + c);
 
}
 
调用:Sumfastcall(10, 8.8, 20, 30);
 
 
     函数体及调用语句如上所示,修饰后的函数名为@Sumfastcall@20,int是32位的,占4个字节,double是64位的,占8个字节,3个32位的变量加1个64位的变量,共20个字节。堆栈和寄存器状态如下(一行表示4个字节):
 
0
 value
0
 rEDX
0
 rECX
0
 rEBP
3000
 k
2000
 j
1000
 i
20
 b
10
 a
 
 <---------EBP
 
 
8.8
 x(8个字节)
30
 c
 
 
10
 ECX
20
 EDX
 
     由于__fastcall和前面两个函数调用约定不太一样,局部变量、函数参数在堆栈中的存放情况和寄存器(主要是ECX和EDX)中的值都有了变化,这些我们都要验证,因此代码也不一样,但大体相同,下面就将它们请出来:
 
#include "iostream.h"
#include "stdio.h"
 
extern "C" __declspec(dllexport) int __fastcall Sumfastcall(int a, double x, int b, int c)
{
     // 声明局部变量
     int i = 1000;
     short j = 2000;
     int k = 3000;
     int rEBP = 0;
     int rECX = 0;
     int rEDX = 0;
     int value = 0;
 
     // 显示 ECX 和 EDX 寄存器的值
     __asm mov rECX, ecx;
     __asm mov rEDX, edx;
     cout << "ECX 和 EDX 寄存器的值:" << endl;
     cout << "ECX: " << rECX << endl;
     cout << "EDX: " << rEDX << endl;
 
     // 显示局部变量的地址
     cout << "局部变量的地址:" << endl;
     cout << &value << "    <-----------value" << endl;
     cout << &rEDX << "    <-----------rEDX" << endl;
     cout << &rECX << "    <-----------rECX" << endl;
     cout << &rEBP << "    <-----------rEBP" << endl;
     cout << &k << "    <-----------k" << endl;
     cout << &j << "    <-----------j" << endl;
     cout << &i << "    <-----------i" << endl;
 
     // 显示存入寄存器的参数的地址, 变量虽然存入了寄存器, 但也在堆栈中
     cout << "显示存入寄存器的参数的地址:" << endl;
     cout << &b << "    <-----------b" << endl;
     cout << &a << "    <-----------a" << endl;
 
     // 显示寄存器的值
     cout << "寄存器:" << endl;
     __asm mov rEBP, ebp;
     printf("0x%08X    <-----------EBP\n", rEBP);
 
     // 显示函数参数的地址
     cout << "函数参数的地址:" << endl;
     cout << &x << "    <-----------x" << endl;