- 被 extern "C" 修饰的变量和函数是按照 C 语言方式编译和连接的。
首先看看 C++ 中,在未加 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++ 中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以 . 来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
其次,看看在未加 extern "C" 声明时,是如何连接的。
假设在 C++ 中,模块 A 的头文件如下:
//模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
实际上,在连接阶段,连接器会从模块 A 生成的目标文件 moduleA.obj 中寻找 _foo_int_int 这样的符号!
对于上面例子,如果 B 模块是 C 程序,而A模块是 C++ 库头文件的话,会导致链接错误;同理,如果B模块是 C++ 程序,而A模块是C库的头文件也会导致错误。
再者,看看加 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 ) ,其结果,将会是 C 语言的编译连接方式:模块 A 编译生成 foo 的目标代码时,没有对其名字进行特殊处理,采用了 C 语言的方式;连接器在为模块 B 的目标代码寻找 foo(2,3) 调用时,寻找的是未经修改的符号名 _foo 。
如果在模块 A 中函数声明了 foo 为 extern "C" 类型,而模块 B 中包含的是 extern int foo( int x, int y ) ,则模块 B 找不到模块 A 中的函数(因为这样的声明没有使用 extern "C" 指明采用C语言的编译链接方式);反之亦然。
所以,综上可知, extern "C" 这个声明的真实目的,就是实现 C++ 与 C 及其它语言的混合编程。
C++ 引用 C 函数的具体例子
在 C++ 中引用 C 语言中的函数和变量,在包含 C 语言头文件(假设为 cExample.h )时,需进行下列处理:
extern "C"
{
#include "cExample.h"
}
因为, C 库的编译当然是用 C 的方式生成的,其库中的函数标号一般也是类似前面所说的 _foo 之类的形式,没有任何参数信息,所以当然在 C++ 中,要指定使用 extern "C" ,进行 C 方式的声明(如果不指定,那么 C++ 中的默认声明方式当然是 C++ 方式的,也就是编译器会产生 _foo_int_int 之类包含参数信息的、 C++ 形式的函数标号,这样的函数标号在已经编译好了的、可以直接引用的 C 库中当然没有)。通过头文件对函数进行声明,再包含头文件,就能引用到头文件中声明的函数(因为函数的实现在库中呢,所以只声明,然后链接就能用了)。
C 引用 C++ 函数的具体例子
在C中引用 C++ 语言中的函数和变量时, C++ 的头文件需添加 extern "C" ,但是在 C 语言中不能直接引用声明了 extern "C" 的该头文件(因为C语言不支持 extern "C" 关键字,所以会报编译错误),应该仅在 C 文件中用 extern 声明 C++ 中定义的 extern "C" 函数(就是 C++ 中用 extern "C" 声明的函数,在 C 中用 extern 来声明一下,这样 C 就能引用 C++ 的函数了,但是 C 中是不用用 extern "C" 的)。
总结
- extern "C" 只是 C++ 的关键字,不是 C 的, 所以,如果在 C 程序中引入了 extern "C" 会导致编译错误。
- 被 extern "C" 修饰的目标一般是对一个全局C或者 C++ 函数的声明
- extern "C" 这个关键字声明的真实目的,就是实现 C++ 与C及其它语言的混合编程