c语言与c++语言中关键字extern的使用

相比static关键字,可能大家对于extern更是陌生一些。我有段时间就是这样,看到static,觉得自己好像知道一点,虽然其实是半知半解。看到extern,我通常会心里一虚,确实很陌生啊。记得查过几次,但是似乎看完就又忘记了,原因就是根本没理解。所以,在昨天把曾让自己半知半解的static关键字讲解完毕后,今天再来写曾经让我半知半解都没有的extern关键字。

链接属性

我们知道一个程序中的多个源文件是分别被编译,最后通过链接器将多个目标文件还有库文件链接起来才形成一个单一完整的可执行文件的。那么如果相同的标识符出现在几个不同的源文件中,它们是代表同一个实体,还是不同的实体呢?

其实,这个问题的答案是由标识符的链接属性决定的。链接属性一共有三种,分别是external(外部)、internal(内部)和none(无)。

  1. none代表没有链接属性,这种标识符有多少个声明就有多少个独立不同的实体;
  2. internal代表内部链接,这种标识符在同一个文件内的所有声明都指向同一实体,但是位于不同源文件的多个声明代表不同实体。
  3. external代表外部链接,这种标识符无论声明多少次,都表示同一个实体。

一般缺省情况下,文件作用域定义的变量都是external属性。代码块作用域声明的变量都是none属性。同时用extern与static可以修改变量的链接属性,static只在变量缺省为external的时候可以将链接属性修改为internal。

extern关键字

关于链接属性的问题,已经讲得很清楚了。按理说,只要是在代码块之外的声明,默认都是external属性。那么我们只需要在代码块里使用extern关键字来声明那些要用到的在别的源文件中定义好的的外部链接的变量不就好了。事实有这么简单吗?

c中的extern

在c语言中确实就是这么简单。请看如下代码,首先是a.c

1
2
3
4
5
6
7
8
# include <stdio.h>

char a = 'A'; // global variable
void msg()
{
printf("%c ", a);
printf("Hello\n");
}
接下来是main.c:
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
char a; // 1.直接在文件作用域声明a,具有external属性
int main(void)
{
//extern char a; // 2.在代码块中使用extern关键字修改链接属性
a = 'B';
printf("%c ", a);
//(void)msg();
msg();
return 0;
}
上述两个文件联合编译,可以通过(只是会有一个msg函数隐式声明的警告)生成可执行文件,运行可得:

B B Hello

如上我们可以发现,1与2两种声明方式其实效果是一样的,都是可以达到我们想要的结果的。

c++中的extern

首先,c++中希望每一份代码的作用尽量清晰。所以cpp中规定,定义与声明是不一样的。一个变量可以在多个文件中声明,被使用,但是,定义它的举动只能出现在一个源文件中。c++作者认为这样可以更好地支持分离式编译。

在c++中,声明使得名字为程序所知,一个文件如果想要使用别处定义的名字必须包含对那个名字的声明。而定义则负责与名字关联的实体的创建和改动。所以如果我们想要在文件之间共享一个变量,记得一定要在声明这个变量时加上extern关键字,告诉编译器,这个变量已经在别处定义好了,定义变量时加与不加const关键字都可以。

当然会有例外: 1. 如果想要在多个文件间共享一个const常量,并且这个常量的初始值不是一个常量表达式,则不论是定义还是声明,都需要加上extern关键字。

  1. 因为函数的声明与定义形式上是有区别的(有无函数体),所以可以将extern关键字省去,但是不省当然也不会有问题。同时,如果函数返回值是const类型的,则不论是声明还是定义,都需要加上extern

总结一下,如果你拿不准,加上extern关键字就行,肯定不会出错。

所以可以参考一下如下代码,首先是a.cpp文件:

1
2
3
4
5
6
7
# include <stdio.h>

char a = 'A'; // global variable
void msg()
{
printf("Hello\n");
}
然后是main.cpp文件:
1
2
3
4
5
6
7
8
#include <stdio.h>
char a;
int main(void)
{
printf("%c ", a);
msg();
return 0;
}
将这两个源文件进行联合编译,如果是c语言,那肯定是通过了,但是c++嘛!报了两个错误!

  1. 第一个错误是msg这个函数未在此作用域内声明,没错,c++就是这么严格,c语言的隐式声明在c++中是不存在的。
  2. 第二个错误是变量a被多次定义!怎么会呢,原来c++规定,如果想要声明一个变量而不是定义一个变量,一定要在变量名前添加关键字extern。必须加extern,否则就是又定义了一个a,extern关键字告诉编译器a在别处已经定义,此处只是声明。否则就是在一个程序中同时定义两个外部链接的变量a,就会报错。

然后我们将main.cpp文件改为如下形式:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
extern char a; //1. 必须加extern,否则就是又定义了一个a,用extern的话告诉编译器a在别处已经定义,此处只是声明
void msg(); //如果没有这个声明,会报错
//char a;
int main(void)
{
//extern char a; //2. 不同位置,作用一样,只是声明作用域不一样
printf("%c ", a);
msg();
return 0;
}
这样就可以编译通过了,执行编译得到的可执行文件生成如下结果:

A Hello

在c++语言中,还有两点需要注意: 1. 在头文件中只进行声明,不进行定义。这个原因是因为,包含头文件其实就是把头文件内容复制到包含的地方,如果在头文件中有定义,那每包含一次,就会定义一次,铁定炸。 2. 一个文件如果因为需要使用某个变量而声明了它,在这个文件中千万不要对这个变量进行改动(对同一个变量的改动只能出现在一个文件中)!比如一个文件中如果有对这个赋值操作,其实就是对这个变量进行重新定义,也铁定炸。

注意!这里extern是用来进行变量的声明的,变量是一个类型的实例。因此在进行跨文件的声明时,类定义与声明并不需要用extern关键字,而一个对象的声明需要用到。

参考资料

  • [1] C++ Primer(第5版)
  • [2] c和指针

c语言与c++语言中关键字extern的使用
http://line.com/2018/08/15/2018-08-15-c-cpp-extern/
作者
Line
发布于
2018年8月15日
许可协议