c++中的几种函数调用约定

C++中的函数调用约定主要针对三个问题:

1、参数传递的方式(是否采用寄存器传递参数、采用哪个寄存器传递参数、参数压桟的顺序等);

2、函数调用结束后的栈指针由谁恢复(被调用的函数恢复还是调用者恢复);

3、函数编译后的名称;

对实例代码有几点说明(使用的平台为vs2012+intel x86架构

1、栈顶指针即为esp;

2、int型占32字节内存;

3、桟顶为小地址端,栈底为大地址端,因此出栈需要增大esp;

下面对C++中见到的stdcall、cdecl、fastcall和thiscall做简要说明。

1、stdcall

stdcall是standard call的缩写,也被称为pascal调用约定,因为pascal使用的函数调用约定就是stdcall。

使用stdcall的函数声明方式为:int __stdcall function(int a,int b)

stdcall的调用约定意味着:

1)采用桟传递全部参数,参数从右向左压入栈;

2)被调用函数负责恢复栈顶指针 ;

3)   函数名自动加前导的下划线,后面是函数名,之后紧跟一个@符号,其后紧跟着参数的尺寸,例如_function@4;

下面给出实例:

int _stdcall funb(int p,int q)           //声明为stdcall方式
{
	return p-q;
}

e=funb(3,4);
012C42F7  push        4                   //参数q入栈
012C42F9  push        3                   //参数p入栈     
012C42FB  call        funb (012C1244h)    //调用函数
012C4300  mov         dword ptr [e],eax   //调用者没有处理esp

函数编译后的汇编代码为:

int _stdcall funb(int p,int q)
{
012C3D80  push        ebp  
012C3D81  mov         ebp,esp                //将esp保存入ebp中
012C3D83  sub         esp,0C0h  
012C3D89  push        ebx                    
012C3D8A  push        esi  
012C3D8B  push        edi  
012C3D8C  lea         edi,[ebp-0C0h]  
012C3D92  mov         ecx,30h  
012C3D97  mov         eax,0CCCCCCCCh  
012C3D9C  rep stos    dword ptr es:[edi]  
	return p-q;
012C3D9E  mov         eax,dword ptr [p]  
012C3DA1  sub         eax,dword ptr [q]  
}
012C3DA4  pop         edi  
012C3DA5  pop         esi  
012C3DA6  pop         ebx  
012C3DA7  mov         esp,ebp  
012C3DA9  pop         ebp  
012C3DAA  ret         8    //注意此处,用被调函数负责恢复esp
以上面函数为例,参数q首先被压栈,然后是参数p(参数从右向左入栈), 然后利用call调用函数,

而在编译时,这个函数的名字被翻译成_funb@8,其中8代表参数为8个字节(2个int型变量)。

另外,stdcall可以用于类成员函数的调用,这种情况下唯一的不同就是,所有参数从右向左依次入栈后,this指针会最后一个入栈。下面给出示例。

class A
{
public:
	A(int a)
	{
		this->val=a;
	}
	int _stdcall fun(int par)  //类成员函数采用stdcall
	{
		return val-par;
	}
private:
	int val;
};

函数调用代码如下:

	A t(3);
	int d,e,f,g;
	g=t.fun(4);

函数调用代码编译后为:

g=t.fun(4);
00DB4317  push        4                  //参数4入栈
00DB4319  lea         eax,[t]  
00DB431C  push        eax                //this指针入栈,下面会验证eax内容即为A的对象的地址
00DB431D  call        A::fun (0DB1447h)  
00DB4322  mov         dword ptr [g],eax  

编译后的代码为:

int _stdcall fun(int par)
	{
00DB3CF0  push        ebp  
00DB3CF1  mov         ebp,esp  
00DB3CF3  sub         esp,0C0h  
00DB3CF9  push        ebx  
00DB3CFA  push        esi  
00DB3CFB  push        edi  
00DB3CFC  lea         edi,[ebp-0C0h]  
00DB3D02  mov         ecx,30h  
00DB3D07  mov         eax,0CCCCCCCCh  
00DB3D0C  rep stos    dword ptr es:[edi]  
		return val-par;
00DB3D0E  mov         eax,dword ptr [this]  
00DB3D11  mov         eax,dword ptr [eax]  
00DB3D13  sub         eax,dword ptr [par]  
	}
00DB3D16  pop         edi  
00DB3D17  pop         esi  
00DB3D18  pop         ebx  
00DB3D19  mov         esp,ebp  
00DB3D1B  pop         ebp  
00DB3D1C  ret         8          //由被调用函数负责恢复栈顶指针,由于参数为int型变量(4字节)和一个指针(32为,4字节),共8字节

下面验证入栈时eax中的内容为A对象的地址。

入栈时eax内容如下,为0x0035F808。


找到内存中0x0035F808的内容,为3,。


再看main函数中实例化对象的代码。


可见,this指针正是通过eax入栈。

由此可见,用于类成员函数时,唯一的不同就是在参数入栈完毕后,this指针会最后一个入栈。

2、cdecl

cdecl是C Declaration的缩写,又称为C调用约定,是C语言缺省的调用约定,采用这种方式调用的函数的声明是:

int function (int a ,int b)                   //不加修饰就是采用默认的C调用约定

int _cdecl function(int a,int b)         //明确指出采用C调用约定

cdecl调用方式规定:

1、采用桟传递参数,参数从右向左依次入栈;

2、由调用者负责恢复栈顶指针;

3、在函数名前加上一个下划线前缀,格式为_function;

要注意的是,调用参数个数可变的函数只能采用这种方式(如printf)。

下面给出实例。

int _cdecl funa(int p,int q)        //采用cdecl方式
{
	return p-q;
}
调用处的代码编译为:

d=funa(3,4);
012C42E8  push        4  
012C42EA  push        3  
012C42EC  call        funa (012C1064h)       //调用funca
012C42F1  add         esp,8                  //调用者恢复栈顶指针esp
012C42F4  mov         dword ptr [d],eax      //返回值传递给变量d

函数编译后的代码为:

int _cdecl funa(int p,int q)
{
012C3D40  push        ebp  
012C3D41  mov         ebp,esp  
012C3D43  sub         esp,0C0h  
012C3D49  push        ebx  
012C3D4A  push        esi  
012C3D4B  push        edi  
012C3D4C  lea         edi,[ebp-0C0h]  
012C3D52  mov         ecx,30h  
012C3D57  mov         eax,0CCCCCCCCh  
012C3D5C  rep stos    dword ptr es:[edi]  
	return p-q;
012C3D5E  mov         eax,dword ptr [p]  
012C3D61  sub         eax,dword ptr [q]  
}
012C3D64  pop         edi  
012C3D65  pop         esi  
012C3D66  pop         ebx  
012C3D67  mov         esp,ebp  
012C3D69  pop         ebp  
012C3D6A  ret                   //注意此处,被调函数没有恢复esp

因此,stdcall与cdecl的区别就是谁负责恢复栈顶指针和编译后函数的名称问题。

cedcal同样可以用于类成员函数的调用。此时,cdedl与stdcall的区别在于由谁恢复栈顶指针

类定义如下:

class A
{
public:
	A(int a)
	{
		this->val=a;
	}
	int _cdecl fun(int par)       //采用cedcl方式
	{
		return val-par;
	}
private:
	int val;
};


调用代码编译如下:

	g=t.fun(4);
013D4317  push        4  
013D4319  lea         eax,[t]  
013D431C  push        eax                     //先入栈参数4,后入栈this指针
013D431D  call        A::fun (013D144Ch)  
013D4322  add         esp,8                   //由调用者恢复栈顶指针
013D4325  mov         dword ptr [g],eax   

3、fastcall

采用fasecall的函数声明方式为:

int __fastcall function(int a,int b)

fastcall调用约定意味着:
1、函数的第一个和第二个(从左向右)32字节参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过桟传递。从第三个参数(如果有的话)开始从右向左的顺序压栈;
2、被调用函数恢复栈顶指针;
3、在函数名之前加上"@",在函数名后面也加上“@”和参数字节数,例如@function@8;

示例代码如下:

int __fastcall func(int p,int q,int r)    //采用fastcall
{
	return p-q-r;
}
调用代码如下:

f=func(3,4,5);
00E74303  push        5           //第三个参数r压桟
00E74305  mov         edx,4       //p q通过ecx和edx传递
00E7430A  mov         ecx,3  
00E7430F  call        func (0E71442h)  
00E74314  mov         dword ptr [f],eax    //调用者不负责恢复栈顶指针esp
函数编译后的代码如下:

int __fastcall func(int p,int q,int r)
{
00E73DC0  push        ebp  
00E73DC1  mov         ebp,esp  
00E73DC3  sub         esp,0D8h  
00E73DC9  push        ebx  
00E73DCA  push        esi  
00E73DCB  push        edi  
00E73DCC  push        ecx  
00E73DCD  lea         edi,[ebp-0D8h]  
00E73DD3  mov         ecx,36h  
00E73DD8  mov         eax,0CCCCCCCCh  
00E73DDD  rep stos    dword ptr es:[edi]  
00E73DDF  pop         ecx  
00E73DE0  mov         dword ptr [q],edx  
00E73DE3  mov         dword ptr [p],ecx  
	return p-q-r;
00E73DE6  mov         eax,dword ptr [p]  
00E73DE9  sub         eax,dword ptr [q]  
00E73DEC  sub         eax,dword ptr [r]  
}
00E73DEF  pop         edi  
00E73DF0  pop         esi  
00E73DF1  pop         ebx  
00E73DF2  mov         esp,ebp  
00E73DF4  pop         ebp  
}
00E73DF5  ret         4     //恢复栈顶指针,由于只有一个参数r被压桟,因此esp+4即可
可以看到,fasecall利用寄存器ecx与edx传递参数,避免了访存带来的开销。适合少量参数提高效率的场合。

4、thiscall

thiscall是 唯一一个不能明确指明的函数修饰 ,因为thiscall只能用于C++类成 员函数的调用,同时thiscall也是C++成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理。

thiscall意味着:

1、采用桟传递参数,参数从右向左入栈。如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针
在所有参数压栈后被压入堆栈;
2、对参数个数不定的,调用者清理堆栈,否则由被调函数清理堆栈



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值