Chinaunix首页 | 论坛 | 博客
  • 博客访问: 10089
  • 博文数量: 10
  • 博客积分: 360
  • 博客等级: 一等列兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-04 11:04
文章分类
文章存档

2011年(1)

2010年(3)

2009年(6)

我的朋友
最近访客

分类: C/C++

2009-05-04 12:45:34

 

Visual C/C++ 的编译器提供了几种函数调用约定,了解这些函数调用约定的含义及它们之间的区别可以帮助我们更好地调试程序。在这篇文章里,我就和大家共同探讨一些关于函数调用约定的内容。

     Visual C/C++ 的编译器支持如下的函数调用约定:

 

关键字

清理堆栈

参数入栈顺序

函数名称修饰(C)

__cdecl

调用函数

à

_函数名

__stdcall

被调用函数

à

_函数名@数字

__fastcall

被调用函数

à

@函数名@数字

thiscall(非关键字)

被调用函数

à

/

 

     上面这张表只简单地列出了每种函数调用约定的特点,既然这篇文章题目的前两个字是剖析,哪能这么容易就完事!?下面就对上面这四种函数调用约定逐个剖析

     一、__cdecl函数调用约定

     这是CC++ 程序默认的函数调用约定,参数按从右到左的顺序压入堆栈,由调用函数负责清理堆栈,把参数弹出栈。也正是因为用来传送参数的堆栈是由调用函数维护的,所以实现可变参数的函数只能使用这种函数调用约定。因为每一个调用它的函数都要包含清理堆栈的代码,所以编译后的可执行文件的大小要比调用__stdcall函数的大。使用这种函数调用约定时,修饰后的函数名只是在原函数名前加上了一个_(下划线),并且不改变函数的大小写。对于__cdecl,我们一般不特别指出,因为它是CC++ 程序默认的函数调用约定,所以只有将编译选项设置成/Gzstdcall)或/Grfastcall)时,我们才有必要在函数名前显式地指出采用这种函数调用约定。下面举一个例子:

 

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@12int32位的,占4个字节,332位的变量,共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

阅读(288) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~