含义
(1) 被 限定的 或 是 类型的:
a. 修饰 的声明。 来说,如果文件a.c需要引用b.c中 int v,就可以在a.c中声明 int v,然后就可以 v。这里需要注意的是,被引用的 v的链接属性必须是外链接( al)的,也就是说a.c要引用到v,不只是取决于在a.c中声明 int v,还取决于 v本身是能够被引用到的。这涉及到 的另外一个话题-- 的 。能够被其他模块以extern 引用到的变量通常是 。还有很重要的一点是, int v可以放在a.c中的任何地方,比如你可以在a.c中的 fun定义的开头处声明extern int v,然后就可以引用到 v了,只不过这样只能在 fun 中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像 声明只能用于文件 似的。
b. 修饰 声明。从本质上来讲, 和函数没有区别。 名是指向函数二进制块开头处的 。如果文件a.c需要引用b.c中的 ,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像 的声明一样, int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件 的范围中。对其他模块中 的引用,最常用的方法是包含这些函数声明的头文件。使用 和包含头文件来引用 有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直截了当的,想引用哪个函数就用extern声明哪个函数。这大概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C 过程中,这种差异是非常明显的。
(2) 被extern "C"修饰的变量和 是按照C语言方式编译和连接的;
未加 “C”声明时的 。
首先看看C++中对类似C的 是怎样编译的。
作为一种 的语言,C++支持 ,而过程式语言C则不支持。 被C++编译后在符号库中的名字与C语言的不同。例如,假设某个 的原型为:
foo( int x, int y );
该 被C 编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了 名、函数参数数量及类型信息,C++就是靠这种机制来实现 的。例如,在C++中, foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地, 中的 除支持 外,还支持类 和 。用户所编写程序的类 可能与 同名,我们以"."来区分。而本质上, 在进行编译时,与 的处理相似,也为类中的 取了一个独一无二的名字,这个名字与 中同名的 名字不同。
2实例
一
未加 "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
#i nclude "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中的函数;反之亦然。
所以,可以用一句话概括 “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):
实现C++与C及其它语言的 。
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。
extern "C"的惯用法
(1)在C++中引用C语言中的 和 ,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
extern "C"
{
#i nclude "cExample.h"
}
而在C语言的头文件中,对其外部 只能指定为 类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。
笔者编写的 C 例子工程中包含的三个文件的 如下:
/* 头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
int add(int x,int y);
#endif
/* 实现文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}
// 实现文件,调用add:cppFile.cpp
extern "C"
{
#i nclude "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才能生成预期的 调用 的结果,否则,使用g++ -o cppFile cppFile.cpp cExample.c 会报错;而当cppFile.cpp 文件中不使用下列语句
extern "C"
{
#i nclude "cExample.h"
}
而改用
#i nclude "cExample.h"
extern "C" int add( int x, int y );
时
g++ -o cppFile cppFile.cpp cExample.c的编译过程会把add 按 的方式解释为_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
#i nclude "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;
}