简介
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。
含义
a.
extern修饰
变量的声明。举例来说,如果文件a.c需要引用b.c中
变量int v,就可以在a.c中声明
externint v,然后就可以
引用变量v。这里需要注意的是,被引用的
变量v的链接属性必须是外链接(
external)的,也就是说a.c要引用到v,不只是取决于在a.c中声明
externint v,还取决于
变量v本身是能够被引用到的。这涉及到
c语言的另外一个话题--
变量的
作用域。能够被其他模块以extern
修饰符引用到的变量通常是
全局变量。还有很重要的一点是,
externint 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语言方式编译和连接的;
作为一种
面向对象的语言,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++中,模块A的头文件如下:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
// 模块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中的函数;反之亦然。
所以,可以用一句话概括
extern“C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。
extern "C"的惯用法
(1)在C++中引用C语言中的
函数和
变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
extern "C"
{
#i nclude "cExample.h"
}
而在C语言的头文件中,对其外部
函数只能指定为
extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
#endif
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}
//
c++实现文件,调用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才能生成预期的
c++调用
c函数的结果,否则,使用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
函数按
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
#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;
}