以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- 函数调用方式的区别[thiscall,__cdecl,__stdcall] (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=64792) |
-- 作者:卷积内核 -- 发布时间:7/21/2008 1:41:00 PM -- 函数调用方式的区别[thiscall,__cdecl,__stdcall] 前几天在CSDN看到yc_8301写的一篇介绍VC中的几种函数调用方法区别的文章,一下子释放了我的好奇心。先前也听说过诸如__cdecl和__stdcall的区别,但也只是泛泛地介绍一下前一种调用(__cdecl)由主调函数负责参数压栈,同时主调函数负责栈(Stack)的恢复。后一种调用(__stdcall)由主调函数负责参数压栈,由函数本身负责栈的恢复。但终究觉得不过隐,觉得__cdecl和__stdcall仍然有些"犹报琵琶半遮面",经YC_8301的一提醒,顿时开朗,在此表示感谢! 本文转自: http://blog.csdn.net/yc_8301/archive/2007/10/08/1814744.aspx 预知更清楚的表述,请朋友们至原文作者Blog: http://blog.csdn.net/yc_8301/ 详查。本文对于原文做了些"篡改",请原作者多多包涵...... 此外本文示例所用的平台:WinXP + VC6.0 -------------------------------------------------------------------------------- 首先,让我们来分个类,调用方法分为两大类另加一个较特殊的__thiscall. 接着介绍一下__thiscall,__thiscall是关于类的一种调用方式.
__cdecl的调用方式介绍: C和C++缺省调用方式 00401068 lea eax,[ebp-8] ;取[ebp-8]地址(ebp-8),存到eax |
-- 作者:卷积内核 -- 发布时间:7/21/2008 1:42:00 PM -- _stdcall调用约定介绍:实际上就是PASCAL,CALLBACK,WINAPI 例子: 看一下相应调用的汇编代码: 00401068 lea eax,[ebp-8] |
-- 作者:卷积内核 -- 发布时间:7/21/2008 1:43:00 PM -- 从以上调用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个字节。由此可以看出:在主调用函数中负责压栈,在被调用函数中负责恢复堆栈。因此不能实现变参函数,因为被调函数不能事先知道弹栈数量,但在主调函数中是可以做到的,因为参数数量由主调函数确定。 |
-- 作者:卷积内核 -- 发布时间:7/21/2008 1:44:00 PM -- 下面再看一下, ebp-8和ebp-4这两个地址实际存储的是什么值,ebp-8地址存储的是n 的值,ebp -4存储的是m的值。说明也是从右到左压栈,进行参数传递。 总结:_stdcall在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且负责恢复堆栈。因此不能实现变参函数,参数传递是从右到左。另外,命名修饰方法是在函数前加一个下划线(_),在函数名后有符号(@),在@后面紧跟参数列表中的参数所占字节数(10进制),如:void Input(int &m,int &n),被修饰成:_Input@8 对于大多数api函数以及窗口消息处理函数皆用CALLBACK,所以调用前,主调函数会先压栈,然后api函数自己恢复堆栈。 如: 2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
4,828.125ms |