__cdecl和__stdcall

 

一、先介绍几个背景知识

1. C/C++程序中函数参数入栈顺序默认是从右至左的。 这么设计是为了支持参数个数动态变化。先从栈中取出的,肯定是最左边的参数,这样就能够支持最右边的参数是可选的。反过来想想,如果采用自左向右的入栈方式,最前面的参数被压在栈底,这种情况下只有事先确定了参数个数,才能通过栈指针的相对位移求得最左边的参数,所以就无法支持参数个数动态变化了。

2. C/C++程序,栈是从高地址向地地址生长的,也即栈底为高地址,栈顶为低地址。结合1和2,其实我们就可以自己写个C++小程序来验证入栈顺序了。默认情况下, 右边的参数先入栈,就是高地址,左边的参数后入栈,就是低地址。

3. C/C++程序,函数调用结束后,可以由调用者负责清空栈,也可以由函数自身负责情况栈。调用者负责清空堆栈的话,因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。

 

二、__cdecl和__stdcall的区别

__cdecl和__stdcall的区别如下表:

调用约定入栈顺序函数调用结束后谁负责清空堆栈函数名修饰说明
__cdecl从右到左调用者见文中描述C和C++程序的缺省调用方式
__stdcall从右到左函数自身见文中描述 

 

函数名修饰

1. C语言函数名修饰:

__cdecl:在函数的前面加上下划线前缀;如 double add(double a, double b) 被修饰为 _add。

__stdcall:函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数;如 double add(double a, double b) 被修饰为_add@16。

2. C++ 语言函数名修饰

    C++的函数名修饰规则有些复杂,但是信息更充分,通过分析修饰名不仅能够知道函数的调用方式,返回值类型,参数个数甚至参数类型。不管__cdecl,还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,后面再跟返回类型,再后面是参数表的开始标识和按照参数类型代号拼出的参数表,参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。如 double add(double a, double b)  被修饰为 ?add@@YANNN@Z 。

X--void    
D--char    
E--unsigned char    
F--short    
H--int    
I--unsigned int    
J--long    
K--unsigned long(DWORD) 
M--float    
N--double    
_N--bool 
U--struct 

     对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”。

    还有当参数列表有指针的时候,指针的方式有些特别,用PA表示指针,用PB表示const类型的指针。后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复。

    举一个简单的例子: int fun(int *p1, int *p2); 会被修饰为(?fun@@YAHPAH0@Z)。

   还有在C++中的成员函数中公有和私有的成员函数的修饰也有相应的表示符。总而言之,在C++环境中的函数名修饰的时候,会带有参数列表的信息,还有返回值的信息,所以在C++中的函数重载就是允许存在的,因为它可以根据你的参数列表选择对应的函数,而显然在我们的C环境下是不允许的。
 

三、如何选择使用__cdecl和__stdcall

何时使用stdcall?

  _stdcall主要用于windows API 。如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用 COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以 WINAPI的样子出现)。

何时使用cdecl?

  _cdecl对于变长参数适用。当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知 道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。

 

四、extern "C"

前面讲到了不论是cdecl还是stdcall,C和C++对函数名修饰都是不同的规则。那么在C和C++之前互相引用的时候,就要使用extern C了。

情况1::在C++中包含C语言写的头文件的时候,将C语言头文件包含在extern "C" 中,这样C++才能调用C声明和定义的函数。如下是一个常见的代码结构,

#ifndef __INCvxWorksh /*防止该头文件被重复引用*/
#define __INCvxWorksh
#ifdef __cplusplus        
extern "C"{           //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
#endif
 
/*…*/
 
#ifdef __cplusplus
}
 
#endif
#endif /*end of __INCvxWorksh*/

 情况2:C中引用C++语言中的函数或者变量时,C++的头文件需要加上extern “C”,但是C语言中不能直接引用声明了extern “C”的该头文件,应该仅在C中将C++中定义的extern “C”函数声明为extern类型。

  /* c++头文件cppExample.h */ 
  #ifndef CPP_EXAMPLE_H 
  #define CPP_EXAMPLE_H 
  extern "C" int add(int x, int y); 
  #endif

  /* c实现文件cFile.c */ 
  extern int add(int x, int y); 
  int main() 
  { 
    add(2, 3); 
    return 0; 
  }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值