VC++中的函数调用惯例

导读:
  我们知道在进行函数调用时,有几种调用方法,主要分为C式,Pascal式.在C和C++中C式调用是缺省的,类的成员函数缺省调用为_stdcall。二者是有区别的,下面我们用实例说明一下:
  1. __cdecl :C和C++缺省调用方式
  例子:
  void Input( int &m,int &n);/*相当于void __cdecl Input(int &m,int &n);*/
  以下是相应的汇编代码:
  00401068 lea eax,[ebp-8] ;取[ebp-8]地址(ebp-8),存到eax
  0040106B push eax ;然后压栈
  0040106C lea ecx,[ebp-4] ;取[ebp-4]地址(ebp-4),存到ecx
  0040106F push ecx ;然后压栈
  00401070 call @ILT+5(Input) (0040100a);然后调用Input函数
  00401075 add esp,8 ;恢复栈
  从以上调用Input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然后压栈ebp-4,然后调用函数Input,最后Input函数调用结束后,利用esp+8恢复栈。由此可见,在C语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复堆栈。
  下面看一下:地址ebp-8和ebp-4是什么?
  在VC的VIEW下选debug windows,然后选Registers,显示寄存器变量值,然后在选debug windows下面的Memory,输入ebp-8的值和ebp-4的值(或直接输入ebp-8和-4),看一下这两个地址实际存储的是什么值,实际上是变量 n 的地址(ebp-8),m的地址(ebp-4),由此可以看出:在主调用函数中进行实参的压栈并且顺序是从右到左。另外,由于实参是相应的变量的引用,也证明实际上引用传递的是变量的地址(类似指针)。
  总结:在C或C++语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复堆栈,实参的压栈顺序是从右到左,最后由主调函数进行堆栈恢复。由于主调用函数管理堆栈,所以可以实现变参函数。另外,命名修饰方法是在函数前加一个下划线(_).
  2. WINAPI (实际上就是PASCAL,CALLBACK,_stdcall)
  例子:
  void WINAPI Input( int &m,int &n);
  看一下相应调用的汇编代码:
  00401068 lea eax,[ebp-8]
  0040106B push eax
  0040106C lea ecx,[ebp-4]
  0040106F push ecx
  00401070 call @ILT+5(Input) (0040100a)
  从以上调用Input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然后压栈ebp-4,然后调用函数Input,在调用函数Input之后,没有相应的堆栈恢复工作(为其它的函数调用,所以我没有列出)
   TAG:
  tmx发布于2007-11-07 15:18:14
  下面再列出Input函数本身的汇编代码:(实际此函数不大,但做汇编例子还是大了些,大家可以只看前和后,中间代码与此例子无关)
  39: void WINAPI Input( int &m,int &n)
  40: {
  00401110 push ebp
  00401111 mov ebp,esp
  00401113 sub esp,48h
  00401116 push ebx
  00401117 push esi
  00401118 push edi
  00401119 lea edi,[ebp-48h]
  0040111C mov ecx,12h
  00401121 mov eax,0CCCCCCCCh
  00401126 rep stos dword ptr [edi]
  41: int s,i;
  42:
  43: while(1)
  00401128 mov eax,1
  0040112D test eax,eax
  0040112F je Input+0C1h (004011d1)
  44: {
  45: printf("/nPlease input the first number m:");
  00401135 push offset string "/nPlease input the first number m"... (004260b8)
  0040113A call printf (00401530)
  0040113F add esp,4
  46: scanf("%d",&m);
  00401142 mov ecx,dword ptr [ebp+8]
  00401145 push ecx
  00401146 push offset string "%d" (004260b4)
  0040114B call scanf (004015f0)
  00401150 add esp,8
  47:
  48: if ( m= s )
  004011B3 mov eax,dword ptr [ebp+8]
  004011B6 mov ecx,dword ptr [eax]
  004011B8 cmp ecx,dword ptr [ebp-4]
  004011BB jl Input+0AFh (004011bf)
  57: break;
  004011BD jmp Input+0C1h (004011d1)
  58: else
  59: printf(" m
  004011BF push offset string "m
  004011C4 call printf (00401530)
  004011C9 add esp,4
  60: }
  004011CC jmp Input+18h (00401128)
  61:
  62: }
  004011D1 pop edi
  004011D2 pop esi
  004011D3 pop ebx
  004011D4 add esp,48h
  004011D7 cmp ebp,esp
  004011D9 call __chkesp (004015b0)
  004011DE mov esp,ebp
  004011E0 pop ebp
  004011E1 ret 8
  最后,我们看到在函数末尾部分,有ret 8,明显是恢复堆栈,由于在32位C++中,变量地址为4个字节(int也为4个字节),所以弹栈两个地址即8个字节。
  由此可以看出:在主调用函数中负责压栈,在被调用函数中负责恢复堆栈。因此不能实现变参函数,因为被调函数不能事先知道弹栈数量,但在主调函数中是可以做到的,因为参数数量由主调函数确定。
  tmx发布于2007-11-07 15:18:31
  下面再看一下,ebp-8和ebp-4这两个地址实际存储的是什么值,ebp-8地址存储的是n 的值,ebp -4存储的是m的值。说明也是从右到左压栈,进行参数传递。
  总结:在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且负责恢复堆栈。因此不能实现变参函数,参数传递是从右到左。另外,命名修饰方法是在函数前加一个下划线(_),在函数名后有符号(@),在@后面紧跟参数列表中的参数所占字节数(10进制),如:void Input(int &m,int &n),被修饰成:_Input@8
  对于大多数api函数以及窗口消息处理函数皆用 CALLBACK ,所以调用前,主调函数会先压栈,然后api函数自己恢复堆栈。
  如:
  push edx
  push edi
  push eax
  push ebx
  call getdlgitemtexta
  你可以想一下,这几个寄存器中存的都是什么?
  参考:msdn
  例子为在VC6.0下debug模式下的Win32 Console反汇编代码。

本文转自
http://blog.cnw.cn/index.php/40339/viewspace-9417
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值