__stdcall 和 __cdecl 的区别浅析
__stdcall 和 __cdecl 的区别浅析
发布时间:2016-12-28 来源:查字典编辑
摘要:1.__cdecl__cdecl是CDeclaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右...

1. __cdecl

__cdecl 是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后也是由调用者负责清除栈的内容,一般来说,这是 C/C++ 的默认调用函数的规则,MS VC 编译器采用的规则则是这种规则2. __stdcall

_stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,由调用者负责把参数压入栈,最后由被调用者负责清除栈的内容,Windows API 所采用的函数调用规则则是这种规则

另外,采用 __cdecl 和 __stdcall 不同规则的函数所生成的修饰名也各为不同,相同点则是生成的函数修饰名前缀都带有下划线,不同的则是后缀部分,当然,这两者最大的不同点就在于恢复栈的方式不同,而且这点亦是最为重要的。

__cdecl 规则要求调用者本身负责栈的恢复工作,在汇编的角度上说,恢复堆栈的位置是在调用函数内,考虑这样一段 C++ 代码(在 VC 下 Debug)

复制代码 代码如下:

#include <cstdio>

void __cdecl func(int param1, int param2, int param3) {

int var1 = param1;

int var2 = param2;

int var3 = param3;

printf("%ldn", long(¶m1));

printf("%ldn", long(¶m2));

printf("%ldn", long(¶m3));

printf("----------------n");

printf("%ldn", long(&var1));

printf("%ldn", long(&var2));

printf("%ldn", long(&var3));

return ;

}

int main() {

func(1, 2, 3);

return 0;

}

注意到 func 函数使用了 __cdecl 进行修饰(其实不需要,因为 VC 下默认的是 __cdecl 规则, 这里是为了更为清晰),生成汇编代码如下:

复制代码 代码如下:

3: void __cdecl func(int param1, int param2, int param3) {

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,4Ch

00401026 push ebx

00401027 push esi

00401028 push edi

00401029 lea edi,[ebp-4Ch]

0040102C mov ecx,13h

00401031 mov eax,0CCCCCCCCh

00401036 rep stos dword ptr [edi]

4: int var1 = param1;

00401038 mov eax,dword ptr [ebp+8]

0040103B mov dword ptr [ebp-4],eax ; 注意var1,var2,var3 压入堆栈的顺序!

5: int var2 = param2;

0040103E mov ecx,dword ptr [ebp+0Ch]

00401041 mov dword ptr [ebp-8],ecx

6: int var3 = param3;

00401044 mov edx,dword ptr [ebp+10h]

00401047 mov dword ptr [ebp-0Ch],edx

............................................... ; 省略了printf的代码

15: return ;

16: }

004010BD pop edi

004010BE pop esi

004010BF pop ebx

004010C0 add esp,4Ch

004010C3 cmp ebp,esp

004010C5 call __chkesp (004011d0)

004010CA mov esp,ebp

004010CC pop ebp

004010CD ret ; 这里是 ret,由调用者(main)恢复堆栈,但如果是 __stdcall 的话,

; 恢复堆栈就在这里进行

*******************************************************************************************************************

18: int main() {

............................................... ; 省略了建立堆栈的代码

19: func(1, 2, 3);

00401118 push 3 ; 将 param3 压入栈

0040111A push 2 ; 将 param2 压入栈

0040111C push 1 ; 将 param1 压入栈

0040111E call @ILT+5(func) (0040100a) ; @ILT+5(func) 是函数func的修饰名,而0040100a则是他的地址

00401123 add esp,0Ch ; 恢复堆栈,__cdecl 规则由调用者(这里是main)恢复堆栈

20: return 0;

00401126 xor eax,eax

21: }

00401128 pop edi

00401129 pop esi

0040112A pop ebx

0040112B add esp,40h

0040112E cmp ebp,esp

00401130 call __chkesp (004011d0)

00401135 mov esp,ebp

00401137 pop ebp

00401138 ret

结果截图

__stdcall 和 __cdecl 的区别浅析1

程序中的栈结构如下图示:

__stdcall 和 __cdecl 的区别浅析2

__stdcall 规则由被调用者本身去调整堆栈,在汇编的角度上说,恢复堆栈的行为发生在调用者函数内,考虑这样一段代码(VC 下Debug):

复制代码 代码如下:

#include <cstdio>

void __stdcall func(int param1, int param2, int param3) {

int var1 = param1;

int var2 = param2;

int var3 = param3;

printf("%ldn", long(¶m1));

printf("%ldn", long(¶m2));

printf("%ldn", long(¶m3));

printf("----------------n");

printf("%ldn", long(&var1));

printf("%ldn", long(&var2));

printf("%ldn", long(&var3));

return ;

}

int main() {

func(1, 2, 3);

return 0;

}

注意到 func 函数使用了 __cdecl 进行修饰(其实不需要,因为 VC 下默认的是 __cdecl 规则, 这里是为了更为清晰),生成汇编代码如下:

复制代码 代码如下:

1: #include <cstdio>

2:

3: void __stdcall func(int param1, int param2, int param3) {

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,4Ch

00401026 push ebx

00401027 push esi

00401028 push edi

00401029 lea edi,[ebp-4Ch]

0040102C mov ecx,13h

00401031 mov eax,0CCCCCCCCh

00401036 rep stos dword ptr [edi]

4: int var1 = param1;

00401038 mov eax,dword ptr [ebp+8]

0040103B mov dword ptr [ebp-4],eax

5: int var2 = param2;

0040103E mov ecx,dword ptr [ebp+0Ch]

00401041 mov dword ptr [ebp-8],ecx

6: int var3 = param3;

00401044 mov edx,dword ptr [ebp+10h]

00401047 mov dword ptr [ebp-0Ch],edx

.............................................. ; 省略 printf 代码

15: return ;

16: }

004010BD pop edi

004010BE pop esi

004010BF pop ebx

004010C0 add esp,4Ch

004010C3 cmp ebp,esp

004010C5 call __chkesp (004011d0)

004010CA mov esp,ebp

004010CC pop ebp

004010CD ret 0Ch ; __stdcall 在这里(被调用函数)恢复堆栈,但如果是 __cdecl 的话,这里是 ret,

; 堆栈的恢复由调用者(这里是 main)来负责

*******************************************************************************************************************

18: int main() {

........................................... ; 省略建立堆栈代码

19: func(1, 2, 3);

00401118 push 3 ; param3 压入堆栈

0040111A push 2 ; param2 压入堆栈

0040111C push 1 ; param1 压入堆栈

0040111E call @ILT+0(func) (00401005) ; @ILT+0(func) 是函数的修饰名,而 00401005 则是调用函数func的地址

20: return 0;

00401123 xor eax,eax

21: }

00401125 pop edi

00401126 pop esi

00401127 pop ebx

00401128 add esp,40h

0040112B cmp ebp,esp

0040112D call __chkesp (004011d0)

00401132 mov esp,ebp

00401134 pop ebp

00401135 ret

运行的结果与使用 __cdecl 规则的一样,两者的栈结构基本一致的,唯一的不同就是调整堆栈(恢复堆栈)的位置以及生成函数的修饰名不同,而这样的不同正是 __stdcall 和 __cdecl 最为重要的不同点

推荐文章
猜你喜欢
附近的人在看
推荐阅读
拓展阅读
相关阅读
网友关注
最新C语言学习
热门C语言学习
编程开发子分类