C++ - __stdcall 与 __cdecl

摘录一

_stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除(也就是自己清除),函数在编译的时候就必须清楚参数个数。

_cdecl 是CDeclaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除。被调用者(也就是自己)不需要求调用者传递多少参数。
这类函数类似printf(...);
scanf(...);

两者共通点:

参数都是从右到左依次入栈


名字修饰约定:

1.C编译时函数名修饰约定:

[cpp]  view plain copy
  1. #ifdef __cplusplus  
  2. extern "C"  
  3. {  
  4. #endif  
  5. __declspec(dllexportint __stdcall add(int x,int y)  
  6. {  
  7.     return x+y;  
  8. }  
  9. #ifdef __cplusplus  
  10. }  
  11. #endif  
①__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数,格式为_functionname@number,例如 :function(int a, int b),其修饰名为:_function@8
上例中就为
_add@8

②__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname
上例子就为_add


2.C++编译时函数名修饰约定:

如例子
__declspec(dllexport) void __stdcall add(int x,char y)
{
int s= x+y;
}

__stdcall方式名字为
?add@@YGXHD@Z

__stdcall规则如下:

1.以'?'标识函数的开始
2.函数名后面跟'@@YG'表示参数的开始
3.参数表以代号表示
X--void
D--char
E--unsigned char
H--int
I--unsigned int
J--long
K--unsigned long
M--float
N--double
_N--bool
...

PA--表示指针,后面的代号表示指针类型,如果相同类型的指针后面出现了,以'0'代替,一个'0'代表一次重复

__declspec(dllexport) void __stdcall add(int *x,char *a,int* y)
{

}
就表示为?add@@YGXPAHPAD0@Z
4.参数表的第一项为该函数的返回类型,其后依次为参数的数据类型.
5.参数表后以'@z'标识整个名字的结束,如果函数无参数,则以'Z'结束.(没有参数同void)

如__declspec(dllexport) __stdcall int add()
{
return 1;
}

名字为:?add@@YGHXZ


__cdecl规则如下:

同__stdcall只是只是函数名后面跟'@@YG'变为了@@YA


摘录二

一、概念
1)_stdcall调用
   _stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,由调用者完成压栈操作, 被调函数自身在返回前清空堆栈。
   WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:  #define WINAPI _stdcall
   
   按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_f unctionname@number.
   Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func@12.
   按C++编译方式,可参看(三)

 2)_cdecl调用
   
_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式, 由调用者完成压栈操作 ,传送参数的内存栈由调用者维护
   _cedcl约定的函数只能被C/C++调用
,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
   按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
   按C++编译方式,可参看(三)

二、区别
     几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,首先,需要了解两者之间的区别: WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除?如果我 们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不 尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发) 平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如 fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用 _cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。

三、编译时函数名修饰约定规则
1)首先介绍一下函数修饰名,C” 或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如 在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同。下面详细说明在C++编译器中修饰名的约定方法,在C编译器中的变化已经在(一)中阐述。

2)C++编译时函数名修饰约定规则
    __stdcall调用约定: 
  1)、以"?"标识函数名的开始,后跟函数名; 
  2)、函数名后面以"@@YG"标识参数表的开始,后跟参数表; 
  3)、参数表以代号表示: 
  X--void , 
  D--char, 
  E--unsigned char, 
  F--short, 
  H--int, 
  I--unsigned int, 
  J--long, 
  K--unsigned long, 
  M--float, 
  N--double, 
  _N--bool, 
  PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复; 
  4)、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前; 
  5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。 
  其格式为" ?functionname@@YG*****@Z "或" ?functionname@@YG*XZ ",例如   int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”   void Test2()  -----“?Test2@@YGXXZ ” 
  __cdecl调用约定: 
  规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。 

四、在C++编译时,引入c库里面的函数的处理
    如果Add(int a, int b)是在c语言编译器编译,而在c++文件使用,由(三)知,因为c编译器和c++编译器对函数名的解释不一样(c++编译器解释函数名的时候要考虑函数参数,这样是了方便函数重载,而在c语言中不存在函数重载的问题),会出现链接错误。 哪么该如何处理呢?这是关键字extern就派上用场了。
   而在c++文件使用c编译器编译过的函数,则需要在c++文件中声明:extern "C" Add(int a, int b),使用extern "C",实质就是告诉c++编译器,该函数是c库里面的函数。请保持我的名称,不要给我生成用于链接的函数修饰名。
   另外顺便说一下extern的另一个用途,extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。#inlcude "***.***"     和 int   f()  等价于   extern int f() ;即 extern 申明的函数前不需要加引用头文件就能用。

参考文献:
博客: http://www.cnblogs.com/Winston/archive/2008/09/11/1289391.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值