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

(0)

相关推荐

  • 关于函数调用方式__stdcall和__cdecl详解

    关于函数调用方式__stdcall和__cdecl详解 __stdcall __cdecl 两者的相同点与不同点 实例 __stdcall __stdcall的全称是standard call.是C++的标准调用方式. 函数参数的入栈顺序为从右到左入栈.函数返回时使用retn x指令,其中x为调整堆栈的字节数.这种方式叫做自动清栈.即被调用的函数的参数个数是固定的,调用者必须严格按照定义传递参数,一个不多,一个不少. __cdecl __cdecl的全称是C Declaration,即C语言默认

  • C/C++函数调用的几种方式总结

    调用函数时,计算机常用栈来存储传递给函数的参数. 栈是一种先进后出的数据结构,栈有一个存储区.一个栈顶指针.栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶).用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改.用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改.函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算.函数计算结束以

  • __stdcall 和 __cdecl 的区别浅析

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

  • JavaScript中const、var和let区别浅析

    在JavaScript中有三种声明变量的方式:var.let.const.下文给大家介绍js中三种定义变量的方式const, var, let的区别. 1.const定义的变量不可以修改,而且必须初始化. const b = 2;//正确 // const b;//错误,必须初始化 console.log('函数外const定义b:' + b);//有输出值 // b = 5; // console.log('函数外修改const定义b:' + b);//无法输出 2.var定义的变量可以修改,

  • boostrapTable的refresh和refreshOptions区别浅析

    在使用bootstrapTable时,刷新数据有两个方法refresh.refreshOptions,其用法相似: <table id="event_table"></table> ... <script> $("#event_table").bootstrapTable('refresh', {url: new_url }); $("#event_table").bootstrapTable('refresh

  • C#中数组、ArrayList、List、Dictionary的用法与区别浅析(存取数据)

    前言 在工作中经常遇到C#数组.ArrayList.List.Dictionary存取数据,但是该选择哪种类型进行存储数据,对于初学者的我一直不知道该怎么取舍.于是抽空好好看了下他们的用法和比较,在这里总结下来,后面有需要改进的再更新. 初始化 数组: int[] buff = new int[6]; ArrayList: ArrayList buff = new ArrayList(); List: List<int> buff = new List<int>(); Dictio

  • fragment中的add和replace方法的区别浅析

    使用 FragmentTransaction 的时候,它提供了这样两个方法,一个 add , 一个 replace ,对这两个方法的区别一直有点疑惑. 我觉得使用 add 的话,在按返回键应该是回退到上一个 Fragment,而使用 replace 的话,那个别 replace 的就已经不存在了,所以就不会回退了.但事实不是这样子的.add 和 replace 影响的只是界面,而控制回退的,是事务. public abstract FragmentTransaction add (int con

  • JavaScript变量声明var,let.const及区别浅析

    var声明变量的作用域限制在其声明位置的上下文中 var x = 0; // x是全局变量,并且赋值为0. console.log(typeof z); // undefined,因为z还不存在. function a() { // 当a被调用时, var y = 2; // y被声明成函数a作用域的变量,然后赋值成2. console.log(x, y); // 0 2 function b() { // 当b被调用时, x = 3; // 全局变量x被赋值为3,不生成全局变量. y = 4;

  • JavaScript中var、let、const区别浅析

    下面通过代码给大家介绍JavaScript中var.let.const区别,具体代码如下所述: //1.var定义的变量可以修改,如果不初始化会输出undefined,不会报错. var a; console.log(a); //undefined //2.let是块级作用域,函数内部使用let定义后,对函数外部无影响. let c = 3; console.log(c) function change(){ let c = 6; console.log(c) } change(); (1)只要

  • Vue.js和Vue.runtime.js区别浅析

    目录 区别 HTML Compiler template render() codesandbox.io 区别   Vue.js Vue.runtime.js 体积 最大 比Vue.js小40% 功能 包含HTML Compiler 不含HTML Compiler cdn引入 选择Vue.js 选择Vue.runtime.js 可见两者最大的区别就是:是否包含HTML Compiler HTML Compiler HTML Compiler 顾名思义是编译 HTML 的工具.在Vue中,页面元素

  • MySQL去重中distinct和group by的区别浅析

    今天在写业务需要对数据库重复字段进行去重时,因为是去重,首先想到的是distinct关键字.于是一小时过去了....(菜鸟一个,大家轻点骂) 我把问题的过程用sql语句演示给大家演示一下 首先我使用的是mybatis-plus,代码如下 QueryWrapper<ProjectCompany> wrapper = new QueryWrapper<>(); wrapper.select("DISTINCT project_id,company_id,company_nam

随机推荐