C语言C/C++:函数的编译格局同调用约定和extern “C”的以

转自:https://www.cnblogs.com/qinfengxiaoyue/archive/2013/02/04/2891908.html

函数在C++编译模式以及C编译形式下的首要不同在:由于C++引入了函数重载(overload),由此编译器对与名函数举行了名称重整(name
mangle)。因而,在C++中挑起

由此其他C函数库时,需要对注解使用的函数做适度的拍卖,以告编译器做出适应之称处理。

函数的调用约定涉及了函数参数的入栈顺序、清栈主体(负责清理栈的主导:函数自身仍旧调用函数者?)、部分号重整。

比方,在C编译格局下出_stdcall、_cdecl等调用约定,在C++编译情势下为闹_stdcall、_cedecl等调用约定。

点滴只复杂修饰的例子:

extern “C” _declspec(dllexport) int __cdecl Add(int a, int b);
//C编译形式导出_cdecl调用约定函数

typedef int (__cdecl*FunPointer)(int a, int b);


转自:http://blog.csdn.net/H2SO2H2SO2/article/details/4207127  

1.编翻译情势

c编译时函数名叫修饰约定规则:

__stdcall调用约定在输出函数叫做前增长一个下划线前缀,后边长一个“@”符号和这参数的字节数,格式为_functionname@number。     

__cdecl调用约定就以输出函数曰前增长一个下划线前缀,格式为_functionname。     

__fastcall调用约定在输出函数称呼前增长一个“@”符号,前边呢是一个“@”符号和那多少个参数的字节数,格式@functionname@number。

它统统未转移输出函数叫作遭之字符大小写,这同pascal调用约定不同,pascal约定输出的函数称凭另外修饰且凡事大写。     

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—–“?test1@@yghpadk@z”       void test2—–“?test2@@ygxxz

__cdecl调用约定:

规则及地点的_stdcall调用约定,只是参数表底始发标识由点的“@@yg”变为“@@ya”。

 

__fastcall调用约定:

规则及地点的_stdcall调用约定,只是参数表底起标识由点的“@@yg”变为“@@yi”。

 

 

2.调用约定

调用约定(Calling
Convention)是赖当次设计语言中为了促成函数调用而起之如出一辙种植协议。这种协议规定了拖欠语言的函数中之参数传送方

典礼、参数是否可变和出于何人来处理堆栈等问题。不同的语言定义了不同之调用约定。

每当C++中,为了允许操作符重载和函数重载,C++编译器往往以某种规则改写每一个入口点的记号名,以便允许和一个名字(具有不同之参

数类型或者是殊的效能域)有差不七只用法,而无会师打破现有的基于C的链接器。这项技能一般为名名称改编(Name Mangling)或者名称修

扮作(Name
Decoration)。许多C++编译器厂商选拔了团结之号修饰方案。

所以,为了要另语言编写的模块(如Visual
Basic应用程序、Pascal或Fortran的应用程序等)可以调用C/C++编写的DLL的函数,必须使

故科学的调用约定来导出函数,并且不要为编译器对而导出的函数举办其他称修饰。

调用约定用来:(一)处理决定函数参数传送时可栈和(二)出栈的顺序(由调用者依然给调用者把参数弹出栈),以及(三)编译器用来认识别函数叫

遂的名称修饰约定等问题。

1、__cdecl

__cdecl是C/C++和MFC程序默认使用的调用约定,也堪当函数注解时抬高__cdecl关键字来手工指定。拔取__cdecl约定时,函数参数按

随从右到左的逐条入栈,并且由于调用函数者管参数弹有库以清理堆栈。因而,实现而转换参数的函数只好用该调用约定。由于每一个下

__cdecl约定的函数都如包含清理堆栈的代码,所以发生的可执行文件大小会相比特别。__cdecl可以描绘成_cdecl。

2、__stdcall

__stdcall调用约定用于调用Win32
API函数。选择__stdcal约定时,函数参数依照从右到左的逐条入栈,被调用的函数在回去前清理传送参

累底堆栈,函数参数个数固定。由于函数体本身知道传进来的参数个数,因而让调用的函数可以于回去前用一久ret
n指令直接清理传递参数的积聚

栈。__stdcall可以形容成_stdcall。

3、__fastcall

__fastcall约定用于对性能要求相当高之场地。__fastcall约定用函数的起左侧先河之有数个大大小小非浮4单字节(DWORD)的参数分别位居

ECX和EDX寄存器,其它的参数还由右为左压栈传送,被调用的函数在回到前清理传送参数的库房。__fastcall可以写成_fastcall。

关键字__cdecl、__stdcall和__fastcall可以一贯加在苟出口的函数前,也可以编译环境之Setting…->C/C++->Code
Generation项选

慎选。它们对应之一声令下执行参数分别吗/Gd、/Gz和/Gr。缺省状态呢/Gd,即__cdecl。当加于出口函数前的根本字和编译环境中之挑选不同时,直

接加在出口函数前之最首要字中。

 

3._stdcall与_cdecl调用约定相比

在“windef.h”头文件中可找到:

#define CALLBACK __stdcall

#define WINAPI __stdcall

#define WINAPIV __cdecl

#define APIENTRY WINAPI

#define APIPRIVATE __stdcall

#define PASCAL __stdcall

#define cdecl _cdecl

#ifndef CDECL#define CDECL _cdecl

#endif

 

差一点我们描绘的各级一个WINDOWS
API函数都是__stdcall类型的,为什么?

先是,我们说一下两者之间的分别:WINDOWS的函数调用时欲由此到库房(STACK,一种先抱后出之囤结构)。当函数调用

好后,栈需要排除,这里虽是题材之根本,怎么着破?假如大家的函数使用了__cdecl,那么栈的解工作是由调用者,用

COM的术语来讲就是是客户来完成的。这样带来了一个高难的问题,不同的编译器发生栈的艺术不尽相同,那么调用者能否健康

的成功清除工作啊?答案是匪克。如若运用__stdcall,下面的题材就缓解了,函数自己解决清除工作。所以,在抢先(开发)平

光之调用中,我们都接纳__stdcall(尽管偶尔是因WINAPI的旗帜出现)。那么为啥还索要_cdecl呢?当我们遭逢这样的套

数要fprintf()它的参数是可变的,不必然长之,被调用者事先不可能清楚参数的尺寸,事后之散工作也无法正常的拓,因而,这

种意况大家不得不接纳_cdecl。

 

注意:

1、_beginthread需要__cdecl的线程函数地址,_beginthreadex和CreateThread需要__stdcall的线程函数地址。

2、一般WIN32的函数都是__stdcall。而且每当Windef.h中有如下的概念:

#define CALLBACK __stdcall

#define WINAPI __stdcall

3、复杂函数注脚或指针的梳洗符示例:

extern “C” _declspec(dllexport) int
__cdecl Add(int a, int b);

typedef int
(__cdecl*FunPointer)(int a, int b);

 

4、extern ”C”
的作用
(参考:http://hi.baidu.com/qinfengxiaoyue/item/8bd89e81d1cbeb5226ebd9b4

怎标准头文件还发出近似以下的布局?

  #ifndef __INCvxWorksh

  #define __INCvxWorksh

  #ifdef __cplusplus

  extern "C" {

  #endif

  /*...*/

  #ifdef __cplusplus

  }

  #endif

  #endif /* __INCvxWorksh */

彰着,头文件被的编译宏“#ifndef
__INCvxWorksh、#define __INCvxWorksh、#endif”
的效能是防范该头文件给重复引用。

那么

#ifdef __cplusplus

extern “C” {

#endif

#ifdef __cplusplus

}

#endif

的来意同时是什么啊?

报经:被extern “C”
修饰的变量和函数是随C语言模式编译和连的;即为落实C++与C语言的插花编程。

略知一二了C++中extern
“C”的设置动机,大家下来具体分析extern “C”平常的运用技巧。

extern
“C”的惯用法:

(1)当C++中引用C语言中的函数和变量,在含蓄C语言头文件(即使为cExample.h)时,需举行下列处理:

extern “C”

{

#include “cExample.h”

}

假使以C语言的条文件被,对这个表面函数只好指定为extern类型,C语言中无辅助extern
“C”申明,在.c文件被带有了extern “C”时会油可是生编

翻译语法错误。

为C++引用C函数例子工程中隐含的老六个文本的源代码如下:

  /* c语言头文件:cExample.h */

  #ifndef C_EXAMPLE_H

  #define C_EXAMPLE_H

  extern int add(int x,int y);

  #endif

  /* c语言实现文件:cExample.c */

  #include "cExample.h"

  int add( int x, int y )

  {

  return x + y;

  }

 

 

  // c++实现文件,调用add:cppFile.cpp

  extern "C"

  {

  #include "cExample.h"

  }

  int main(int argc, char* argv[])

  {

  add(2,3);

  return 0;

  }

要C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或者注明接口函数时,应加extern
“C” { }。

(2)于C中引用C++语言中之函数和变量时,C++的峰文件被的函数讲明需要加加前缀extern
“C”,可是以C语言中不克直接引用

就由extern
“C”修饰了之函数注脚或变量的头文件(因为C编译情势不协理extern “C”
关键字),应该当C中以索要引用的C++

中**函数的宣示为extern类型。**

为C引用C++函数例子工程被含的老六个文件的源代码如下:

  //C++头文件 cppExample.h

  #ifndef CPP_EXAMPLE_H

  #define CPP_EXAMPLE_H

  extern "C" int add( int x, int y );

  #endif

  //C++实现文件 cppExample.cpp

  #include "cppExample.h"

  int add( int x, int y )

  {

  return x + y;

  }

 

  /* C实现文件 cFile.c

  /* 但这样会编译出错:#include "cExample.h",因为C编译不支持extern "C" 关键字 */

  extern int add( int x, int y );

  int main( int argc, char* argv[] )

  {

  add( 2, 3 );

  return 0;

  }

 

5、MFC提供了有些高大,可以用AFX_EXT_CLASS来代替__declspec(DLLexport),并修饰类名,从而导出类,

AFX_API_EXPORT来修饰函数,AFX_DATA_EXPORT来修饰变量

AFX_CLASS_IMPORT:__declspec(DLLexport)

AFX_API_IMPORT:__declspec(DLLexport)

AFX_DATA_IMPORT:__declspec(DLLexport)

AFX_CLASS_EXPORT:__declspec(DLLexport)

AFX_API_EXPORT:__declspec(DLLexport)

AFX_DATA_EXPORT:__declspec(DLLexport)

AFX_EXT_CLASS:#ifdef _AFXEXT

AFX_CLASS_EXPORT

#else

AFX_CLASS_IMPORT

6、DLLMain负责起初化(Initialization)和了(Termination)工作,每当一个初的历程或欠过程的新的线程访问DLL时,或

啊看DLL的诸一个过程或线程不再动用DLL或者终止时,都碰面调用DLLMain。不过,使用TerminateProcess或

TerminateThread截止进程或线程,不会见调用DLLMain。

7、一个DLL在内存中单爆发一个实例

DLL程序与调用其出口函数的顺序的涉及:

1)、DLL与经过、线程之间的关系

DLL模块于射到调整用其的经过的虚拟地址空间。

DLL使用的内存从调用进程的虚拟地址空间分配,只可以为拖欠过程的线程所走访。

DLL的词柄可以让调用进程使;调用进程的语句柄可以吃DLL使用。

DLL可以出温馨之数据段,但无和谐之堆栈,使用调用进程的仓库,与调用它的应用程序相同之库情势。

2)、关于共享数据段

DLL定义之全局变量可以被调用进程看;DLL可以拜调用进程的大局数据。使用同一DLL的诸一个过程都发协调的DLL全局

变量实例。假若多单线程并发访问同变量,则需用并机制;对一个DLL的变量,假如要每个使用DLL的线程都来谈得来

的值,则当运用线程局部存储(TLS,Thread
Local Strorage).