Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7893548
  • 博文数量: 701
  • 博客积分: 2150
  • 博客等级: 上尉
  • 技术积分: 13233
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-29 16:28
个人简介

天行健,君子以自强不息!

文章分类

全部博文(701)

文章存档

2019年(2)

2018年(12)

2017年(76)

2016年(120)

2015年(178)

2014年(129)

2013年(123)

2012年(61)

分类: C/C++

2013-06-27 17:46:09

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

二、含义
(1) 被extern限定的函数或变量是extern类型的:
a.  extern修饰变量的声明。
举例来说,
如果文件a.c需要引用b.c中变量int v,
可以在a.c中声明
 extern int v,

然后就可以引用变量v。

这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,
也就是说a.c要引用到v,不只是取决于在a.c中声明externint v,还取决于变量v本身是能够被引用到的。
这涉及到c语言的另外一个话题--变量的作用域。

能够被其他模块以extern修饰符引用到的变量通常是全局变量。
还有很重要的一点是,
extern int v可以放在a.c中的任何地方,
比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,
只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。

对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

b.  extern修饰函数声明。
从本质上来讲,变量和函数没有区别。
函数名是指向函数二进制块开头处的指针。
如果文件a.c需要引用b.c中的函数,
比如在b.c中原型是
 int fun(int mu),

那么就可以在a.c中声明
 extern int fun(int mu),

然后就能使用fun来做任何事情。

就像变量的声明一样,externint fun(int mu)可以放在a.c中任何地方,
而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,
最常用的方法是包含这些函数声明的头文件。

使用extern和包含头文件来引用函数有什么区别呢?
extern的引用方式比包含头文件要简洁得多!
extern的使用方法是直截了当的,想引用哪个函数就用extern声明哪个函数。
这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,
会加速程序的编译(确切的说是预处理)的过程,节省时间。
在大型C程序编译过程中,这种差异是非常明显的。

(2) 被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"通常的使用技巧。


五、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
externint 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;
}


注意:
这里如果用GCC编译的时候,请先使用gcc -c选项生成cExample.o,
再使用g++ -o cppFile cppFile.cpp cExample.o才能生成预期的c++调用c函数的结果,
否则,使用g++ -o cppFile cppFile.cpp cExample.c编译器会报错;
而当cppFile.cpp 文件中不使用下列语句
extern "C"
{
  #include "cExample.h"
}


而改用


#include "cExample.h"
extern "C" int add( int x, int y );



g++ -o cppFile cppFile.cpp cExample.c


的编译过程会把add函数按c++的方式解释为_foo_int_int这样的符号。


如果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
/* 这样会编译出错:#i nclude "cppExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
  add( 2, 3 );
  return 0;
}
阅读(1601) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~