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);
}
|