一、定义
extern是一种“外部声明”的关键字,字面意思就是在此处声明某种变量或函数,在外部定义。
如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显式地初始化变量:
/*
e.g.2 以下为3个文件
*/
//test.h
int i=6;//声明,并定义变量
//test2.h
extern int i;//只声明变量
//main.cpp
#include "test.h"
#include "test2.h"
int main()
{
return 0;
}
注意: 变量能且只能被定义一次,但是可以被声明多次。 extern对应的关键字是static,static表明变量或者函数只能在本模块中使用。因此,被static修饰的变量或者函数不可能被extern C修饰。
二、原理
C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。为了将程序分为许多文件,则需要在文件中共享代码,例如一个文件的代码可能需要另一个文件中中定义的变量。
为了支持分离式编译,C++允许将声明和定义分离开来。变量的声明规定了变量的类型和名字,即使一个名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。定义则负责创建与名字关联的实体,定义还申请存储空间。
三、其他用法——extern "C"
当它与"C"一起连用时,例如:extern “C” void func(int a);这句话就是告诉编译器,在编译 func 函数名时,按着 C 的规则去翻译相应的函数名,而不是 按C++ 的方式。这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。
这个功能主要用在下面的情况:
- C++代码调用C语言代码
- 在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到
看一个简单的例子:
有moduleA、moduleB两个模块,B调用A中的代码,其中A是用C语言实现的,即modeleA是一个.c文件,而B是利用C++实现的,即cpp文件,下面给出一种实现方法:
//moduleA头文件
#ifndef __MODULE_A_H //对于模块A来说,这个宏是为了防止头文件的重复引用
#define __MODULE_A_H
int fun(int, int);
#endif
//moduleA实现文件moduleA.C //模块A的实现部分并没有改变
#include"moduleA"
int fun(int a, int b)
{
return a+b;
}
//moduleB头文件
#ifndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#include"moduleA.h"
… //其他代码
}
#endif
//moduleB实现文件 moduleB.cpp //B模块的实现也没有改变,只是头文件的设计变化了
#include"moduleB.h"
int main()
{
cout<<fun(2,3)<<endl;
}
编译,
编译失败了,因为对于.c文件,windows下的编译器会在c语言符号(变量和函数)前加上“_”,即fun函数被编译后的符号为_fun
而在c++的main中,fun被编译为“?fun@@YAHHH@Z”。两个符号不一致,所以就会在链接过程中报错。
我们修改一下,在moduleB中加入extern "C"
#ifndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
extern "C" { //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
#include"moduleA.h"
#endif
//其他代码
}
#endif
编译成功
四、C和C++编译和链接的区别
具体原理:
C++支持函数重载,而C是不支持的。拿一个函数原型void one(int i,int j);举个例子。该函数在C编译器编译后在符号库中的名字为:_one,而C++编译器在编译后会产生类似_one_int_int的名字这个名字包含了函数名字、参数数量、参数类型等信息。C++就是通过这种方式实现了函数重载。
假如这个函数定义在了一个C++文件中,同时在.c文件中调用了这个函数。那么,当C编译器编译到这个函数的时候,会将这个函数名改为_one,而C++编译器会将这个函数的名称改为_one_int_int,也就是在C编译器得到的目标文件中one()函数是由_one,来引用的。而在C++编译器得到的目标文件中one()函数是由_one_int_int来代替的。
当连接器工作的时候,它不管上层采用了什么样的语言,它只认目标文件中的符号,于是连接器在C文件中发现调用了one函数,但是在目标文件中却找不到_one的符号,因此会显示连接出错。而加上extern "C"后,是为了向编译器指明这段代码按照C语言的方式进行编译,从而解决了这个问题。
- 不可以将extern "C" 添加在函数内部
- 如果函数有多个声明,可以都加extern "C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
- 除extern "C", 还有extern "FORTRAN" 等。
参考: