__stdcall 和 __cdecl 的区别浅析

 更新时间:2013年03月06日 12:08:23   作者:  
__stdcall 和 __cdecl 的区别浅析,需要的朋友可以参考一下

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("%ld\n", long(&param1));
  printf("%ld\n", long(&param2));
  printf("%ld\n", long(&param3));
  printf("----------------\n");
  printf("%ld\n", long(&var1));
  printf("%ld\n", long(&var2));
  printf("%ld\n", 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 规则由被调用者本身去调整堆栈,在汇编的角度上说,恢复堆栈的行为发生在调用者函数内,考虑这样一段代码(VC 下Debug):

复制代码 代码如下:

#include <cstdio>

void __stdcall func(int param1, int param2, int param3) {
  int var1 = param1;
  int var2 = param2;
  int var3 = param3;

  printf("%ld\n", long(&param1));
  printf("%ld\n", long(&param2));
  printf("%ld\n", long(&param3));
  printf("----------------\n");
  printf("%ld\n", long(&var1));
  printf("%ld\n", long(&var2));
  printf("%ld\n", 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++实现产生随机数和相应的猜拳小游戏实例代码

    C++中没有自带的random函数,要实现随机数的生成就需要使用rand()和srand()。下面这篇文章主要给大家介绍了关于C++实现产生随机数和相应的猜拳小游戏的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-09-09
  • 详解C语言编程中预处理器的用法

    详解C语言编程中预处理器的用法

    这篇文章主要介绍了C语言编程中预处理器的用法,包括介绍了C和C++混合编程的情况,需要的朋友可以参考下
    2016-02-02
  • 带你了解如何用C++合并两个有序链表

    带你了解如何用C++合并两个有序链表

    这篇文章主要介绍了c++ 如何合并两个有序链表,小编感觉这篇文章还不错,希望能帮助大家更好的理解和学习C++,感兴趣的朋友可以了解下
    2021-08-08
  • C++ 如何实现一个日期类

    C++ 如何实现一个日期类

    通过对类和对象的学习,理解了类是对象的抽象描述,实现日期类涉及定义年月日属性及成员函数如打印日期、日期加减,重点介绍了运算符重载的概念和作用,通过代码示例展示了如何实现一个日期类,包括头文件和源文件的分离编写
    2024-10-10
  • C++实现查找二叉树中和为某一值的所有路径的示例

    C++实现查找二叉树中和为某一值的所有路径的示例

    这篇文章主要介绍了C++实现查找二叉树中和为某一值的所有路径的示例,文中的方法是根据数组生成二叉排序树并进行遍历,需要的朋友可以参考下
    2016-02-02
  • Qt编写提示进度条的实现示例

    Qt编写提示进度条的实现示例

    进度条在很地方都可以使用到,Qt自带的进度条或者操作系统的进度条样式,不够炫,本文就介绍一下Qt编写自定义控件的提示进度条的实现示例,感兴趣的可以了解一下
    2021-12-12
  • OpenCV实现简单录屏功能

    OpenCV实现简单录屏功能

    这篇文章主要为大家详细介绍了OpenCV实现简单录屏功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • C++中nullptr 和 NULL 的区别及用法

    C++中nullptr 和 NULL 的区别及用法

    nullptr是常数,nullptr_t是它的类型.在需要分别使用空指针或空指针类型的上下文中使用每一个.今天通过本文给大家介绍C++ nullptr 和 NULL 的使用区别,需要的朋友参考下吧
    2021-07-07
  • C++实现LeetCode(151.翻转字符串中的单词)

    C++实现LeetCode(151.翻转字符串中的单词)

    这篇文章主要介绍了C++实现LeetCode(151.翻转字符串中的单词),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C++实现宿舍管理查询系统

    C++实现宿舍管理查询系统

    这篇文章主要为大家详细介绍了C++实现宿舍管理查询系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03

最新评论