__stdcall 和 __cdecl 的区别浅析

2016-02-19 10:59 6 1 收藏

下面图老师小编跟大家分享一个简单易学的__stdcall 和 __cdecl 的区别浅析教程,get新技能是需要行动的,喜欢的朋友赶紧收藏起来学习下吧!

【 tulaoshi.com - 编程语言 】

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() {

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

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

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 规则由被调用者本身去调整堆栈,在汇编的角度上说,恢复堆栈的行为发生在调用者函数内,考虑这样一段代码(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() {

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

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

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 最为重要的不同点

来源:http://www.tulaoshi.com/n/20160219/1596276.html

延伸阅读
标签: 化妆水
油性皮肤使用紧肤水;健康皮肤使用爽肤水;干性皮肤使用柔肤水;对混合皮肤来说,T字部位使用紧肤水,其它部位使用柔肤水和爽肤水皆可。 化妆水和爽肤水的区别-爽肤水和柔肤水的区别 化妆水和爽肤水的区别 爽肤水、柔肤水、收敛水统称化妆水,是一种透明液态的化妆品,涂抹在皮肤的表面,用来清洁肌肤、保持肌肤的健康。爽...
标签: 养生 健康
所谓干型葡萄酒(干白、干红)仅指葡萄酒中含糖量的多少,并没有其他的含义。按照标准的规定,干型葡萄酒的含糖量在4.0g/L以下。由于这种类型的酒含糖量低,没有甜味,所以更多地表现出葡萄的果香、发酵产生的酒香和陈酿留下的醇香。传统意义上的葡萄酒都是甜型葡萄酒,含糖量都在50.0g/L以上。 干红葡萄酒的“干”是从香Tulaoshi.Com槟...
标签: 筒灯 射灯 灯饰
1、吊顶开孔 家居筒灯的开孔尺寸一般有2寸,2.5寸,3寸三种,开孔尺寸越大,筒灯整体高度越高,对你的吊顶深度要求也就越大,一般而言,筒灯全部拉伸后高度在10公分到13公分之间,所以安装筒灯的天花板吊顶深度一般要在13公分以上,如果吊顶太浅,则可以考虑装天花射灯或横装筒灯。而天花射灯安装吊顶要求只要6到8公分。 2、光...
同属凤梨科,但产地不同 市场上出售的凤梨和菠萝为不同种水果。凤梨与菠萝同属凤梨科,从生物学角度上来说,这两者是没有很大的区别的,可以说凤梨就是菠萝。但是产地不同,气候环境的差异使得它们在形态,风味口感等方面又有着一些不易发觉的区别,从市场的角度来说,凤梨是凤梨,菠萝是菠萝,这两者是不同的水果。 两者区别:有无内刺 ...
酸梨的功效 对于这种河北特产,我们应该要对它的功效有一定的了解,这样才能够让我们在冬季比较少水果的时候,食用酸梨的营养价值,让我们知道酸梨对我们身体的发育有很大的帮助。 酸梨学名安梨,分布河北燕山一带和东北。果实约200克,扁圆形,成熟时比较酸,有点淡淡的甜。耐储运,以前多为冬季在水果比较稀少时吃,经过几个月的储存,...

经验教程

660

收藏

18
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部