7-6 内部链接
在第7.3节——局部变量中,我们提到:“标识符的链接属性决定了其他同名声明是否指向同一对象”,并讨论了局部变量不具有链接关系no linkage的特性。
全局变量和函数标识符可具有内部链接关系internal linkage或外部链接关系external linkage。本节将讲解内部链接关系的情况,外部链接关系将在第7.7节——外部作用域与变量前向声明中阐述。
具有内部链接关系internal linkage的标识符可在单个翻译单元内被访问和使用,但无法从其他翻译单元访问。这意味着若两个源文件中存在名称相同且具有内部链接关系的标识符,这些标识符将被视为独立存在(不会因重复定义而导致ODR违规)。
关键要点
具有内部链接的标识符可能完全不可见于链接器。或者,它们可能可见于链接器,但仅标记为可在特定翻译单元中使用。
相关内容
我们在第2.10课——预处理器介绍中介绍了翻译单元。
具有内部链接的全局变量
具有内部链接的全局变量有时被称为内部变量internal variables。
要使非常量全局变量成为内部变量,我们使用static关键字。
#include <iostream>
static int g_x{}; // non-constant globals have external linkage by default, but can be given internal linkage via the static keyword
const int g_y{ 1 }; // const globals have internal linkage by default
constexpr int g_z{ 2 }; // constexpr globals have internal linkage by default
int main()
{
std::cout << g_x << ' ' << g_y << ' ' << g_z << '\n';
return 0;
}
const 和 constexpr 全局变量默认具有内部链接(因此无需 static 关键字——若使用该关键字,将被忽略)。
以下是多个文件使用内部变量的示例:
a.cpp:
[[maybe_unused]] constexpr int g_x { 2 }; // this internal g_x is only accessible within a.cpp
main.cpp:
#include <iostream>
static int g_x { 3 }; // this separate internal g_x is only accessible within main.cpp
int main()
{
std::cout << g_x << '\n'; // uses main.cpp's g_x, prints 3
return 0;
}
该程序输出:

由于 g_x 是每个文件内部的变量,main.cpp 并不知道 a.cpp 也有一个名为 g_x 的变量(反之亦然)。
对于进阶读者
上文中的static关键字属于存储类限定符storage class specifier,它同时规定了名称的链接关系和存储期。最常用的存储类限定符包括static、extern和mutable。存储类限定符这一术语主要出现在技术文档中。
对于进阶读者
C++11标准(附录C)阐明了const变量默认具有内部链接的原因:“由于const对象可在C++中作为编译时值使用,此特性促使程序员为每个const显式提供初始化值。该特性允许用户将const对象置于被多个编译单元包含的头文件中。”C++设计者旨在实现两点:
- const对象应可用于常量表达式。为满足此要求,编译器必须在编译时看到定义(而非声明)才能进行求值。
- const对象应能通过头文件传播。
具有外部链接的对象只能在单个翻译单元中定义而不违反ODR规则——其他翻译单元必须通过前向声明访问这些对象。若常量对象默认具有外部链接,则它们仅能在包含定义的单个翻译单元中用于常量表达式,且无法通过头文件有效传播,因为将头文件#include到多个源文件会导致ODR违规。
具有内部链接的对象可在每个需要它们的翻译单元中定义而不会违反 ODR。这使得 const 对象可以被放置在头文件中,并被包含到任意数量的翻译单元中而不违反 ODR。由于每个翻译单元都包含定义而非声明,这确保了这些常量可在翻译单元内的常量表达式中使用。
内联变量
Inline variables(可具有外部链接而不引发 ODR 冲突)直至 C++17 才被引入。
具有内部链接关系的函数
如上所述,函数标识符也具有链接关系。函数默认具有外部链接关系(我们将在下一课中讲解),但可通过static关键字设置为内部链接关系:
add.cpp:
// This function is declared as static, and can now be used only within this file
// Attempts to access it from another file via a function forward declaration will fail
[[maybe_unused]] static int add(int x, int y)
{
return x + y;
}
main.cpp:
#include <iostream>
int add(int x, int y); // forward declaration for function add
int main()
{
std::cout << add(3, 4) << '\n';
return 0;
}

该程序无法链接,因为函数 add 在 add.cpp 文件外部不可访问。
单定义规则与内部链接
在第2.7课——前向声明与定义中,我们提到单定义规则规定:一个对象或函数在单个文件或程序中不能有多个定义。
但需特别说明:不同文件中定义的内部对象(及函数)被视为独立实体(即使名称和类型完全相同),因此不会违反单定义规则。每个内部对象仅有一个定义。
静态命名空间与无名命名空间
在现代C++中,使用static关键字为标识符赋予内部链接的方式正逐渐失宠。无名命名空间能为更广泛的标识符(如类型标识符)提供内部链接,且更适合为大量标识符赋予内部链接。
我们在第7.14节——无名命名空间与内联命名空间中详细探讨了无名命名空间。
为何要赋予标识符内部链接?
通常赋予标识符内部链接有两个原因:
- 存在某个标识符需要确保其他文件无法访问。这可能是我们不希望被篡改的全局变量,或是不希望被调用的辅助函数。
- 为避免命名冲突而采取的严谨措施。由于内部链接的标识符不会暴露给链接器,它们仅可能与同一翻译单元内的名称发生冲突,而非整个程序范围。
许多现代开发指南建议将所有不需跨文件使用的变量和函数设为内部链接。若能严格执行,这是极佳的实践。
当前我们建议采用更灵活的最低标准:仅对明确需要禁止跨文件访问的标识符设置内部链接。
最佳实践
当你有明确理由禁止其他文件访问时,请为标识符赋予内部链接。
考虑将所有不希望被其他文件访问的标识符设为内部链接(为此请使用无名命名空间)。
快速摘要
// Internal global variables definitions:
static int g_x; // defines non-initialized internal global variable (zero initialized by default)
static int g_x{ 1 }; // defines initialized internal global variable
const int g_y { 2 }; // defines initialized internal global const variable
constexpr int g_y { 3 }; // defines initialized internal global constexpr variable
// Internal function definitions:
static int foo() {}; // defines internal function
我们在第7.12节——作用域、存储期和链接总结 提供了全面的总结。

浙公网安备 33010602011771号