zoukankan      html  css  js  c++  java
  • __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 最为重要的不同点

  • 相关阅读:
    2014,成为更好程序员的7个方法
    联想集团大裁员,血淋漓的教训:公司只能给你位置,却无法给你未来!(转)
    Oracel数据库连接时出现:ORA-12518:监听程序无法分发客户机连
    Could not find a version that satisfies.... No matching distribution found for .....
    深度学习(七)U-Net原理以及keras代码实现医学图像眼球血管分割
    深度学习(六)keras常用函数学习
    np.random.random()函数 参数用法以及numpy.random系列函数大全
    2019最新win10 安装tensorflow1.4(GPU/CPU)+cuda8.0+cudnn8.0-v6 + keras 安装CUDA失败 导入tensorflow失败报错问题解决
    c++ 网络编程课设代码 网络编程入门教程 ---目录
    c++ 网络编程(十一) LINUX下 初步制作基于HTTP的WEB服务器
  • 原文地址:https://www.cnblogs.com/yewei/p/2941315.html
Copyright © 2011-2022 走看看