• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

谈谈VC++中两种函数调用方式的区别

发布: 2007-5-26 13:55 | 作者: 佚名 | 来源: 互连网 | 查看: 170次 | 进入软件测试论坛讨论

领测软件测试网
  我们知道在进行函数调用时,有几种调用方法,主要分为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之后,没有相应的堆栈恢复工作(为其它的函数调用,所以我没有列出)
  
  下面再列出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 < n*(n+1)/2,Please input again!\n");
  004011BF push offset string " m < n*(n+1)/2,Please input agai"... (00426060)
  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个字节。
  
  由此可以看出:在主调用函数中负责压栈,在被调用函数中负责恢复堆栈。因此不能实现变参函数,因为被调函数不能事先知道弹栈数量,但在主调函数中是可以做到的,因为参数数量由主调函数确定。
  
  下面再看一下,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

延伸阅读

文章来源于领测软件测试网 https://www.ltesting.net/


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备10010545号-5
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网