您的位置:首页 > 编程语言 > C语言/C++

C 调用约定和 C++调用约定

2017-06-02 00:23 501 查看
函数的调用约定

常用的 有两种:__stdcall 和 __cdecl 

主要区别在于调用方式和对堆栈的处理不同。

__stdcall:C++调用方式和函数导出方式。

__cdecl : C调用方式和导出方式。

具体:

cdecl调用约定:
cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:
int function (int a ,int b) //不加修饰就是C调用约定
int __cdecl function(int a,int b)//明确指出C调用约定
cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由有向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。

stdcall调用约定:
在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。
stdcall调用约定声明的语法为:
int __stdcall function(int a,int b)
stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。

注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。

_stdcall 与_cdecl是两种不同的函数调用约定,区别在函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。对于参数可变的函数如printf,使用的是_cdecl调用约定,Win32的API函数都遵循_stdcall调用约定。在VC++开发环境中,默认的编译选项是_cdecl,对于那些需要_stdcall调用约定的函数,必须显式的加上_stdcall.

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(¶m1));

  printf("%ld\n", long(¶m2));

  printf("%ld\n", long(¶m3));

  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(¶m1));

  printf("%ld\n", long(¶m2));

  printf("%ld\n", long(¶m3));

  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 最为重要的不同点
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  函数