什么是内部链接?
在《局部变量》中,我们指出:“标识符的链接属性决定了同名的其他声明是否指向同一实体”,并阐明局部变量不具有链接属性。
全局变量和函数标识符则可具有内部链接或外部链接。本节讨论内部链接,外部链接将在《外部链接与变量前向声明》中介绍。
具有内部链接的标识符可在单个翻译单元内被查看和使用,但对其他翻译单元不可见。这意味着,若两个源文件各自含有同名且均具有内部链接的标识符,它们将被视为独立实体,不会触发“一次定义规则”(ODR)冲突。
关键洞察
具有内部链接的标识符可能对链接器完全不可见;也可能可见,但被标记为“仅限当前翻译单元”。
相关阅读
翻译单元的概念见《预处理器简介》。
具有内部链接的全局变量
具有内部链接的全局变量常被称为内部变量。
要使非常量全局变量具有内部链接,需使用 static
关键字:
#include <iostream>
static int g_x {}; // 非常量全局变量默认具有外部链接,可用 static 显式改为内部链接
const int g_y { 1 }; // const 全局变量默认具有内部链接
constexpr int g_z { 2 }; // constexpr 全局变量默认具有内部链接
const
与 constexpr
全局变量默认即为内部链接,无需再写 static
;若额外添加 static
,将被忽略。
多文件示例
a.cpp
[[maybe_unused]] constexpr int g_x { 2 }; // 仅在 a.cpp 可见的内部 g_x
main.cpp
#include <iostream>
static int g_x { 3 }; // 仅在 main.cpp 可见的内部 g_x
int main()
{
std::cout << g_x << '\n'; // 使用 main.cpp 的 g_x,输出 3
return 0;
}
程序输出:
3
由于 g_x
在每个文件中均为内部链接,main.cpp
并不知道 a.cpp
也存在同名变量,反之亦然。
存储类说明符与链接属性
上述 static
用法属于存储类说明符(storage class specifier),它同时设定标识符的链接属性及存储期。常见的存储类说明符包括 static
、extern
和 mutable
,该术语多见于技术文档。
C++11 标准附录 C 解释了为何 const
变量默认内部链接:
“由于
const
对象在 C++ 中可作为编译期常量使用,该特性促使程序员为每个const
提供显式初始化值。这使得const
对象能够放在被多个编译单元包含的头文件中,而不会违反 ODR。”
设计目标:
const
对象需能用于常量表达式,故编译器必须在编译期看到其定义而非仅声明;const
对象需能通过头文件传播。
若默认外部链接,const
对象只能在定义所在翻译单元用于常量表达式;且头文件被多个源文件包含时会触发 ODR 冲突。内部链接允许每个翻译单元拥有独立定义,既满足常量表达式需求,又避免冲突。
直到 C++17 引入 inline
变量,才可在无 ODR 风险的前提下实现外部链接的常量。
具有内部链接的函数
函数标识符同样具有链接属性。函数默认为外部链接,但可用 static
设为内部链接:
add.cpp
// static 函数仅在本文件可见,其他文件无法通过前向声明调用
[[maybe_unused]] static int add(int x, int y)
{
return x + y;
}
main.cpp
#include <iostream>
int add(int x, int y); // 对 add 的前向声明
int main()
{
std::cout << add(3, 4) << '\n';
return 0;
}
该程序无法链接,因为 add
在 add.cpp 中为内部链接,其他文件不可访问。
一次定义规则(ODR)与内部链接
2.7 课指出,同一程序中对象或函数不得有多于一个定义。
然而,定义在不同文件中的内部对象(或函数)被视为独立实体(即使名字和类型相同),故不违反 ODR。每个内部对象仅有一个定义。
static 与匿名命名空间
在现代 C++ 中,使用 static
提供内部链接的做法已逐渐失宠。匿名命名空间可为更广泛类别的标识符(如类型名)赋予内部链接,且更适于批量声明。
匿名命名空间将在《匿名与内联命名空间》中讨论。
为何使用内部链接?
通常有两个理由:
- 防止标识符被其他文件访问(如保护全局变量或隐藏辅助函数);
- 严格避免命名冲突——内部链接标识符仅能在同一翻译单元内冲突,不会扩散到整个程序。
多数现代开发规范建议:凡不打算供外部使用的变量与函数均应具有内部链接(推荐使用匿名命名空间)。若你具备相应规范执行力,这是良好实践。
当前建议
至少应给明确不希望被外部使用的标识符赋予内部链接;理想情况下,给所有此类标识符加上内部链接(使用匿名命名空间实现)。
最佳实践
当存在明确理由阻止外部访问时,为标识符赋予内部链接。
考虑为所有不对外暴露的标识符统一使用内部链接(通过匿名命名空间实现)。
快速小结
// 内部全局变量定义
static int g_x; // 未显式初始化,默认零初始化
static int g_x { 1 }; // 显式初始化
const int g_y { 2 }; // const 变量默认内部链接
constexpr int g_y { 3 }; // constexpr 变量默认内部链接
// 内部函数定义
static int foo() {}; // 内部链接函数
完整总结见《作用域、存储期与链接小结》。