随笔 - 27  文章 - 88  trackbacks - 0
<2009年7月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

常用链接

留言簿(4)

文章分类(3)

文章档案(3)

搜索

  •  

积分与排名

  • 积分 - 129537
  • 排名 - 198

最新评论

阅读排行榜

评论排行榜

(转)C++中extern “C”含义深层探索
 
 
1.引言

  C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。
  2.从标准头文件说起

  某企业曾经给出如下的一道面试题:

  面试题
  为什么标准头文件都有类似以下的结构?

 


#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


  的作用又是什么呢?我们将在下文一一道来。
 
  3.深层揭密extern "C"

  extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。

  被extern "C"限定的函数或变量是extern类型的;

  extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:

  extern int a;


  仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

  通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

  与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

  被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;

  未加extern “C”声明时的编译方式

  首先看看C++中对类似C的函数是怎样编译的。

  作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );


  该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

  _foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
  同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

  未加extern "C"声明时的连接方式

  假设在C++中,模块A的头文件如下:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif


  在模块B中引用该函数:

// 模块B实现文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);


  实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

  加extern "C"声明后的编译和连接方式

  加extern "C"声明后,模块A的头文件变为:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif


  在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

  (1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

  (2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

  如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

  所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):
  实现C++与C及其它语言的混合编程。
  明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。
  4.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文件中将C++中定义的extern "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" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}


  如果深入理解了第3节中所阐述的extern "C"在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C++引用C函数和C引用C++函数的惯用法。对第4节给出的示例代码,需要特别留意各个细节。
 

posted on 2008-06-17 12:40 Macaulish 阅读(94322) 评论(36)  编辑 收藏 引用

FeedBack:
# re: (转)C++中extern “C”含义深层探索[未登录] 2008-06-22 22:52 hdqqq
c++编译的时候,对函数名进行修饰,用于实现函数充载,而c里面没有这个,所以需要用extern “C” 在对头文件进行声明的时候加以区分。这个用于链接的时候进行函数名查找。  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2009-02-12 17:00 zero
好文  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2009-06-08 11:50 wuzhuayu
讲的很不错哦  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2009-07-01 09:32 Samuel
如此在C 中引用C++的函数,必须先修改C++的源文件?  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索[未登录] 2009-07-17 13:22 echo
清楚  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2009-07-23 00:08 rob
C引用c++的函数,只需要用extern声明,不需要extern "c",也不需要修改c++源文件。

有人能解析obj文件格式么,呵呵。  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2009-07-31 16:45 ggg
好文章,受教了  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2009-08-07 17:29 love c
讲的非常好!详细而又浅显易懂,谢了!  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索[未登录] 2009-08-24 23:04 dudu
thanks a lot
it is really clear  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2009-10-27 13:44 Dim
受教了……今天笔试才考到过……  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2010-06-13 11:50 koral
很清楚!谢谢!  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2010-06-27 17:45 cloved
受益了..  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2010-08-31 11:03 李伟男
基本上讲的比较清楚了,非常感谢!后面的一个例子再深入一些,会更好。
extern “C” 是一个双向都需要用到的语法表示,就是说在cpp引用c头文件,或者c引用cpp文件时都需要用到。但extern “C” 永远只能在cpp引用时出现,c引用时不允许存在。当cpp引用c中的函数时,需要在cpp使用的头文件中声明extern “C”,当c引用cpp中的函数时,需要在cpp使用的头文件中用extern “C”声明,这样编译器在编译时会对函数名进行特殊处理,以使其能够被c引用。如果不进行声明,那么当c引用这个头文件时,就会找不到函数,因为cpp的函数命名规则中包含变量类型,而c编译后的函数名不包含这些。  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2010-12-13 11:27 xinqikan
<C++ Primer中文版第四版>
* const 限定符:http://www.xinqikan.com/2010/1203/ch02lev1sec4.html
* 变量和基本类型http://www.xinqikan.com/2010/1203/ch02.html  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-03-02 02:45 飘飘
好文
谢谢  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-03-17 17:13 Zero Lee
1."extern" before function declaration in c header file is optional, when c++ calling c function defined in c file;
2. If want to only call one c function in c file, and tell code-reader which function will be called in current c++ file, written as below:
extern "C" void func(int a, int b);
#include "cXX.h"

  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-04-06 09:22 cm
看了您的描述,对您最后的那段代码做一个简单的推论,在cppExample.h
中不允许重载,尽管是在CPP文件中,由于声明了extern "C",所以那段代码是通过C语言编译器来编译的,在库中保留的是_add,看如下代码,查探它的事实性:
//cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
int add( int x, int y );
float add( float x, float y );
#endif  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-04-06 09:24 cm
//cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
float add( float x, float y )
{
return x + y;
}
//main.cpp
extern int add( int x, int y );
extern float add( float x, float y );
#include <stdio.h>
int main( int argc, char* argv[] )
{
printf("%d\n",add( 2, 3 ));
printf("%f\n",add( 2.0, 3.0 ));
return 0;
}  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-04-06 09:25 cm
这段代码经过编译之后,会出现重定义错误:
--------------------Configuration: ghf - Win32 Debug--------------------
Compiling...
main.cpp
E:\C++垃圾箱\ghf\main.cpp(7) : error C2668: 'add' : ambiguous call to overloaded function
Error executing cl.exe.

ghf.exe - 1 error(s), 0 warning(s)
从此可以看出,我们的理论是正确的!
谢谢楼主哈,让我学了点东西。  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-04-29 12:18 jarson
这段代码应该是:
//cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "c" {
int add( int x, int y );
float add( float x, float y );
}
#endif
才会后面所说的编译错误吧?  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-05-17 10:21 alanwen
你说对了,呵呵,应该是这样!@jarson
  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-05-28 22:34 dave
如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。
//VC2005验证了一下, 这个结论不成立  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-05-28 23:15 dave
撤销上面的跟帖,楼主的结论是正确的. @dave
  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-06-27 11:29 路过人士
我怎么觉得dll里面的extern没用呢  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-07-05 11:38 丢丢
好贴!!  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-07-22 16:09 hhx
太好了,非常感谢楼主。  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-07-25 14:23 chasefornone
好文,获益匪浅,非常感谢  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-08-24 22:45 大卫
@dave
我觉得“如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。”和楼主的解释矛盾啊,既然已经在A中声明了foo为extern "C"类型,在模块B中调用的话应该用extern标识符啊。  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-08-24 23:21 大卫
是不是在cpp文件中不能出现 extern int add( int x, int y );啊?我在cpp中用
extern "C"
{
#include "moduleA.h"
}
可以,用extern int add( int x, int y );就报错。其中 moduleA.h中定义如下:
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2011-08-24 23:32 大卫
用vs2008一试,在cpp文件里面加入extern int i;是可以的,上面的说法不对。不过加extern int add( int x, int y );是不可以的,报错。
另外上面的回复中写错了一个地方:
其中 moduleA.h中定义如下:
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int add( int x, int y );
#endif
  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索[未登录] 2011-11-24 16:10 YY
受益非浅  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2012-05-23 08:30 kevin00000000
好文  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2013-03-24 11:22 wwy
@rob
dumpbin.exe  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2013-07-11 15:21 creasy
讲的很详细 ,实用 谢谢了  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2014-04-10 12:25 Bryan
挺好的~  回复  更多评论
  
# re: (转)C++中extern “C”含义深层探索 2015-04-16 22:18 别逸
写的不错!  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理