8.6 关键字:extern

功能

修饰符extern用在变量和函数的声明前,表示“此变量/函数是在别处定义的”,提示编译器在遇到此函数或者变量时在其他模块中寻找它们的定义。

1. extern修饰变量

extern修饰变量时经常用于:定义全局变量时,在.h文件中通过extern声明,然后在.cpp中定义它。

如果想声明一个变量而非定义它,就使用关键字extern并且不要显式地初始化变量:

/*
 * 在所有函数外声明/定义全局变量a
 * */

extern int a;      // 声明一个全局变量
int a;             // 定义一个全局变量
extern int a = 0;  // 定义一个全局变量并初始化, 抵消了extern的作用
int a = 0;         // 定义一个全局变量并初始化

声明而非定义全局变量时必须加extern关键字修饰,否则即使没有显式的初始化它也会被加载进BSS区初始化为0。

2. extern修饰函数

对于函数而言,声明和定义本来就是有区别的(定义函数要有函数体,声明函数不需要函数体),因此声明函数时extern关键字是可有可无的(或者函数声明本身就是extern的)。

假设我们在foo.h中声明了foo_func()函数,然后在foo.cpp中定义它,当我们想要在bar.cpp中引用该函数时,使用extern声明和直接#include foo.h还是有一些区别的。

假设我们有foo.hfoo.cpp文件,并且在其中定义了foo()等函数:

/*
 * foo.h
 */
#ifndef FOO_H
#define FOO_H

void foo();

#endif

/*
 * foo.cpp
 */
#include <iostream>

void foo() {
    std::cout << "foo()" << std::endl;
}

2.1 使用#include引入函数声明

/*
 * main.cpp
 */
#include "foo.h"

int main() {
    foo();
    return 0;
}

2.2 使用extern引入函数声明

在前面的例子中,我们在mian.cpp#include "foo.h"从而可以使用声明在foo.h中的函数。但是这样的做法会将foo.h中所有的声明都引入到main.cpp中,假设我们只需要foo()函数就显得很没有必要。我们可以通过在函数声明前加上extern表明这个函数是定义在其他.cpp文件中的,这样一方面可以使得代码更佳清晰简洁,另一方面也会加快编译期间预处理的速度。

extern void foo();

int main() {
    foo();
    return 0;
}

重定义问题

C++中重定义错误包含如下两种情况:

  • 情况一:多个源文件包含了同个头文件,且头文件中包含了某个全局变量或者非内联函数的定义

  • 情况二:某个源文件多次包含同一个头文件

对于情况一而言,我们应当尽量避免在头文件中定义全局变量或者非内联函数,全局变量可以在头文件中用extern声明然后在源文件中定义,函数定义可以加上inline关键字变成内联函数。由于编译器会将类、内联函数以及const变量默认视为定义它们的源文件所私有,因此这些类型可以定义在头文件中。

对于情况二而言,我们可以在头文件中添加头文件保护符:

// 法一
#ifndef FOO_H
#define FOO_H
/* foo.h的内容 */
#endif

// 法二: Google编码规范不推荐
#pragma once

Reference

[1] https://www.cnblogs.com/renyuan/archive/2012/11/30/2796801.html

[2] https://zhuanlan.zhihu.com/p/141833130