调用时只要用result =function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,要由函数调用者或者函数本身修改栈,使栈恢复原样。
在参数传递中,有两个很重要的问题必须得到明确说明:当参数个数多于一个时,按照什么顺序把参数压入堆栈函数调用后,由谁来把堆栈恢复原装。在高级语言中,通过函数调用约定来解决这两个问题。常见的调用约定有:
stdcall,cdecl,fastcall,thiscall,nakedcall
(1)cdecl调用约定:
cdecl调用约定又称为C调用约定,是C/C++语言缺省的调用约定,它的定义语法是:
int function (int a ,int b) //不加修饰就是C调用约定 int __cdecl function(int a,int b)//明确指出C调用约定 按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数vararg的函数(如printf)只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。返回值在j寄存器eax中。
#include<iostream> using namespace std ; int function(int a,int b); int _cdecl func(int c); int fun2(int num1,...); int main() { cout << function(1,2) <<endl; cout << func(1) <<endl; cout <<fun2(2,3,3) <<endl; return 0; } #include<iostream> using namespace std ; int func(int a,int b) { return a+b; } void test() { cout << func(1,2) <<endl; } int main() { test(); system("pause"); return 0; } test()函数的部分反汇编代码如下:
00FA3E95 push eax 00FA3E96 push 2 00FA3E98 push 1 00FA3E9A call func (0FA150Ah) 00FA3E9F add esp,8 注意:这里调用者在恢复堆栈 00FA3EA2 mov edi,esp
(2)stdcall调用约定:
stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。
stdcall调用约定声明的语法为:
int __stdcall function(int a,int b)
stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 ,3)返回值也是在eax中。
#include<iostream> using namespace std ; int _stdcall func(int a,int b); int main() { cout << func(1,2) <<endl; return 0; }
#include<iostream> using namespace std ; int _stdcall func(int a,int b,int c) { return a+b+c; } void test() { cout << func(1,2,3) <<endl; } int main() { test(); system("pause"); return 0; } func函数部分反汇编代码如下:
01214107 pop edi 01214108 pop esi 01214109 pop ebx 0121410A mov esp,ebp 0121410C pop ebp 0121410D ret 0Ch 函数本身在恢复堆栈
其声明语法为:int _fastcall function(int a,int b)
#include<iostream> using namespace std ; int _fastcall func(int a,int b,int c,int d) { return a+b+c+d; } int main() { cout << func(1,2,3,4) <<endl; system("pause"); return 0; } 部分反汇编代码:
45:cout << func(1,2,3,4) <<endl; 003B489E mov esi,esp 003B48A0 mov eax,dword ptr ds:[003C136Ch] 003B48A5 push eax 003B48A6 push 4 003B48A8 push 3 003B48AA mov edx,2 //可以发现参数入栈是从右到左的,前两个参数是放在寄存器edx和ecx中的。 003B48AF mov ecx,1 003B48B4 call func (03B14ECh)
(4)thiscall调用约定: thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成 员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,th iscall意味着: 参数从右向左入栈 如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针 在所有参数压栈后被压入堆栈。 对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈
#include<iostream> using namespace std ; #include<stdarg.h> class A { public: int function1(int a,int b); int function2(int a,...); }; int A::function1 (int a,int b) { return a+b; } int A::function2(int a,...) { va_list ap; va_start(ap,a); int result = 0; for(int i = 0; i < a; i ++) { result += va_arg(ap,int); } return result; } void call() { A a; a.function1 (1,2); a.function2(3,1,2,3); } int main() { call(); system("pause"); return 0; } 部分反汇编代码如下:
31:A a; 32: a.function1 (1,2); 00BA30EE push 2 00BA30F0 push 1 00BA30F2 lea ecx,[a] 00BA30F5 call A::function1 (0BA14DDh) 参数个数确定的函数调用,this指针通过ecx传递给被调用者,this没有入栈。 33: a.function2(3,1,2,3); 00BA30FA push 3 00BA30FC push 2 00BA30FE push 1 00BA3100 push 3 00BA3102 lea eax,[a] 这里引入this指针 00BA3105 push eax this指针入栈 00BA3106 call A::function2 (0BA14E2h) 参数个数不确定的函数调用 00BA310B add esp,14h 可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似于cdecl
(5)nakedcall 这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计