__stdcall与__cdecl的区别分析。有需要的朋友可以参考一下。
1.__cdecl__cdecl是C声明的缩写,表示C语言默认的函数调用方法:所有参数从右到左堆栈,调用者负责将参数推入堆栈,最后调用者负责清空堆栈的内容。一般来说,这是C/C调用函数的默认规则。这是MS编译器采用的规则。2.__stdcall_stdcall是StandardCall的缩写,是C的标准调用方法:所有参数从右向左堆栈,调用者负责将参数推入堆栈,最后被调用者负责清空堆栈的内容。Windows API采用的函数调用规则就是这个规则。
另外__cdecl和__stdcall规则不同的函数生成的修改名也不同。相同点是生成函数的修改名前缀加下划线,不同点是后缀。当然,两者最大的区别是栈的还原方式不同,这也是最重要的一点。
__cdecl规则要求调用方自己负责堆栈的恢复。从汇编的角度来看,恢复堆栈的位置在调用函数内。考虑这样一段C代码(VC下调试)复制代码如下:#include cstdio
void __cdecl func(int param1,int param2,int param 3){ int var 1=param 1;int var2=param2int var3=param3
printf(%ldn ,long(param 1));printf(%ldn ,long(param 2));printf(%ldn ,long(param 3));printf(- n );printf(%ldn ,long(var 1));printf(%ldn ,long(var 2));printf(%ldn ,long(var 3));返回;}
int main() { func(1,2,3);返回0;}注意func函数是用__cdecl修饰的(实际上并不需要,因为VC下的默认规则是__cdecl,这里为了清楚起见),生成的汇编代码如下:
复制代码如下:3: void _ _ cdeclfunc (intparam1,intparam2,int param 3){ 00401020 push ebp 00401021 move BP,esp 00401023subesp,4Ch 00401026 push ebx 00401027 push ESI 00401028 push EDI 00401029 lea EDI,[ebp-4Ch]0040102C mov ecx,13h 00000401038 mov eax,dword ptr[ebp 8]0040103 b mov dword ptr[ebp-4],eax;注意var1,var2,var3压入堆栈的顺序!5:int var 2=param 2;0040103E mov ecx,dword ptr[ebp 0Ch]00401041 mov dword ptr[ebp-8],ecx 6:int var 3=param 3;00401044 mov edx,dword ptr [ebp 10h]
......省略printf的代码。
15:返回;16:} 004010 BD pop EDI 004010 be pop ESI 004010 BF pop ebx 004010 c 0 add esp,4Ch004010C3 cmp ebp,esp 004010 c 5 call _ _ chk esp(004011d 0)004010 ca mov esp,ebp 004010 cc pop ebp 004010 CD ret;这里是ret,调用者(main)还原堆栈,但是如果是__stdcall,这是堆栈恢复的地方。
*******************************************************************************************************************
18: int main() {
......构建堆栈的代码被省略了。
19: func(1,2,3);00401118推3;将param3推入堆栈0040111 a push 2;将param2推入堆栈0040111 c push 1;将param1推入堆栈0040111 call @ ILT 5(func)(0040100 a);@ILT 5(func)是函数func的修饰名,0040100a是他的地址00401123 add esp,0Ch堆栈恢复,__cdecl规则由调用者(这里是main) stack 20恢复:返回0;00401126 xor eax,eax 21:} 00401128 pop EDI 00401129 pop ESI 0040112 a pop ebx 0040112 b add esp,40h0040112E cmp ebp,esp 00401130 call _ _ chk esp(004011d 0)00401135 mov esp,ebp00401137 pop ebp00401138 ret
结果截图
程序中的堆栈结构如下:
__stdcall规则是被调用者自己调整堆栈。从组装的角度来看,恢复堆栈的行为发生在调用方的函数中。考虑这样一段代码(VC下调试):复制代码如下:#include cstdio
void __stdcall func(int param1,int param2,int param 3){ int var 1=param 1;int var2=param2int var3=param3
printf("% LD n ",long(param 1));printf("% LD n ",long(param 2));printf("% LD n ",long(param 3));printf("- n ");printf("% LD n ",long(var 1));printf("% LD n ",long(var 2));printf("% LD n ",long(var 3));返回;}
int main() { func(1、2、3);返回0;}注意到func(消歧义)函数使用了_ _ _ cdcl进行修饰(其实不需要,因为你呢下默认的是_ _ _ cdcl规则,这里是为了更为清晰),生成汇编代码如下:
复制代码代码如下:1:#包括CST dio 2:3:void _ _ STD call func(int param 1,int param2,int param 3){ 00401020 push ebp 00401021 mov ebp,esp00401023 sub esp,4Ch 00401026 push ebx 00401027 push ESI 00401028 push EDI 00401029 lea EDI,[ebp-4Ch]0040102 c mov ECE职位ptr[EDI]4:int var 1=00401038 mov eax,ptr dword[ebp 8]0040103 b mov ptr dword[ebp-4],eax 5:int var 2=param 2;0040103E mov ecx,ptr dword[ebp 0ch]00401041 mov ptr dword[ebp-8],ecx 6:int var 3=param 3;00401044 mov edx,ptr dword[ebp 10h]
…………………………………………………。省略印夫代码
15:返回;16:} 004010 BD EDI 004010 be pop ESI 004010 BF pop ebx 004010 c 0 add esp,4Ch004010C3 cmp ebp,esp 004010 c 5 call _ _ chesp(004011 d0)004010 ca mov esp,ebp 004010 cc pop ebp 004010 CD ret 0ch;_ _ _ _ _标准呼叫在这里(被调用函数)恢复堆栈,但如果是_ _ _ cdcl的话,这里是ret,你知道吗?堆栈的恢复由调用者(这里是(手)来负责
-好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧,好吧
18: int main()
………………………………………。省略建立堆栈代码
19: func(1、2、3);00401118推进3;停止3压入堆栈0040111A推2;停止2压入堆栈0040111C推力1;停止一压入堆栈0040111E呼叫@ ILT 0(func)(00401005);@ILT 0(函数)是函数的修饰名,而00401005 则是调用函数func(消歧义)的地址20:返回0;00401123 xor eax,eax 21:} 00401125 pop EDI 00401126 pop ESI 00401127 pop ebx 00401128添加esp,40h0040112B cmp ebp,esp0040112D呼叫_ _ chkcesp(004011 d0)00401132 mov esp,ebp00401134 pop ebp00401135 ret运行的结果与使用_ _ _ cdcl规则的一样,两者的栈结构基本一致的,唯一的不同就是调整堆栈(恢复堆栈)的位置以及生成函数的修饰名不同,而这样的不同正是_ _ _ _ _标准呼叫和_ _ _ cdcl最为重要的不同点