在 C++ 代码运行之前,编译器和连接器的需要完成它们的工作。如果想要程序能够顺利地跑起来,我们需要了解 C++ 中的两个个至关重要的概念 translation unit (编译单元,简称 TU) 和 one definition rule (定义一次规则,下面简称 ODR) 。
所谓的 TU 就是一个 source file (源文件) 和 它所引用的 header files (头文件) 。而 linkage (链接) 可以理解为一个变量或者一个函数是否只作用于某个 TU 。
而链接又分为 internal linkage (内部连接) 和 external linkage (外部链接) 。内部连接,顾名思义,如果一个变量是属于内部连接的话,那么它在每一个 TU 里面都是独一无二的。只要满足以下条件,对象的链接方式均为内部连接[^1]:
const int*
、const double
等等)constexpr char*
)static int age
)在 a.h 文件中声明并初始化 const int* iptr
,然后被包含到不同的 TU 中。我们不难发现每个独立的 TU 都有自己的一个 iptr
,它们互不干扰互不影响。
// a.h
const int* iptr = new int(111);
//a.cpp
#include "a.h"
void scope_a()
{
std::cout << *iptr << "\n";
}
// b.h 为空文件
// b.cpp
#include "b.h"
#include "a.h"
void scope_b()
{
*iptr = 222;
std::cout << *iptr << "\n";
}
// main.cpp
#include "a.h"
#include "b.h"
int main()
{
*iptr = 333;
std::cout << *iptr << "\n"; // 打印 333
scope_a(); // 打印 111
scope_b(); // 打印 222
}
与内部链接不同的是,链接方式属于外部链接的变量,无论在多少个 TU 里,都是同一个对象。如果我们试图在 a.h 文件中声明并初始化一个变量 int num = 111
的话,程序会出错。因为这里的定义并不满足,上述内部链接的 4 个条件。
// a.h
int num = 111;
void scope_a();
//a.cpp
#include "a.h"
void scope_a()
{
std::cout << num << "\n";
}
// b.cpp
#include "b.h"
#include "a.h"
void scope_b()
{
std::cout << num << "\n";
}
// main.cpp
#include "a.h"
#include "b.h"
int main()
{
std::cout << num << "\n";
scope_a();
scope_b();
}
很遗憾,上面的代码发生了连接错误:
"int num" already defined in A.obj
"int num" already defined in A.obj
one or more multiply defined symbols found
num
的声明和定义是在 a.h 同时进行,所以出现这样的报错是正常的,因为违反了 ODR 。为了修正这样的错误,extern
关键字派上用场。值得注意的是,如果要使用 extern
去修饰一个变量的话,该变量的声明和定义必须在源文件内进行。
// a.h
void scope_a();
//a.cpp
#include "a.h"
extern int num = -1;
void scope_a()
{
std::cout << num << "\n";
}
// b.cpp
#include "b.h"
#include "a.h"
extern int num;
void scope_b()
{
num = 0;
std::cout << num << "\n";
}
// main.cpp
#include "a.h"
#include "b.h"
extern int num;
int main()
{
num = 1;
std::cout << num << "\n";
scope_a();
scope_b();
std::cout << num << "\n";
}
输出结果:
1
1
0
0
一般情况下,我们都会避免用全局变量。因为全局变量的使用不当,会给程序带来一定的风险。
^1 :https://docs.microsoft.com/en-us/cpp/cpp/program-and-linkage-cpp?view=vs-2019