什么是外部链接?
在上一课(内部链接)中,我们讨论了内部链接将标识符的作用范围限制在单个源文件。本课将探讨外部链接的概念。
具有外部链接的标识符不仅在其定义文件可见,也可通过前向声明在其他源文件中使用。从这个意义上说,外部链接的标识符是真正的“全局”标识符,可在整个程序的任何位置使用。
关键洞察
具有外部链接的标识符对链接器可见,使得链接器能够完成两项工作:
- 将某翻译单元中使用的标识符连接到另一翻译单元中的正确定义;
- 对
inline
标识符进行去重,保留唯一正式定义(inline
变量与函数)。
函数默认具有外部链接
在《多文件程序》中你已了解到,可以调用定义在另一文件中的函数,因为函数默认具有外部链接。
若要在其他文件调用某函数,必须在使用该函数的每个文件中提供函数前向声明;前向声明告知编译器函数存在,而链接器负责将调用连接到实际定义。
示例:
a.cpp
#include <iostream>
void sayHi() // 具有外部链接,可供其他文件调用
{
std::cout << "Hi!\n";
}
main.cpp
void sayHi(); // sayHi 的前向声明,使其在本文件可用
int main()
{
sayHi(); // 调用另一文件定义的函数,链接器会完成连接
return 0;
}
程序输出:
Hi!
若 sayHi()
具有内部链接,链接器将无法连接调用与定义,会产生链接错误。
具有外部链接的全局变量
具有外部链接的全局变量常称为外部变量。
要使全局变量可供其他文件使用,可使用关键字 extern
:
int g_x { 2 }; // 非常量全局变量默认具有外部链接,无需 extern
extern const int g_y { 3 }; // const 全局变量可用 extern 显式声明为外部
extern constexpr int g_z { 3 }; // constexpr 亦可用 extern(但极少有用,见下文警告)
非常量全局变量默认即具有外部链接,无需额外标记。
通过 extern
进行变量前向声明
要在其他文件使用已定义的外部全局变量,必须在每个需要使用的文件中给出变量前向声明。变量前向声明同样使用 extern
,且不带初始化值。
示例:
main.cpp
#include <iostream>
extern int g_x; // 变量 g_x 的前向声明
extern const int g_y; // const 变量 g_y 的前向声明
int main()
{
std::cout << g_x << ' ' << g_y << '\n'; // 输出 2 3
return 0;
}
变量定义文件:
a.cpp
// 变量定义
int g_x { 2 }; // 非常量变量默认外部链接
extern const int g_y { 3 }; // 用 extern 显式指定外部链接
上述示例中,main.cpp
与 a.cpp
引用的是同一个 g_x
。虽然 g_x
在 a.cpp
内定义并初始化,但通过前向声明,main.cpp
也能使用其值。
注意:关键字 extern
在不同上下文含义不同:
- 在定义处:
extern
表示“具有外部链接”; - 在前向声明处:
extern
表示“这是一个外部变量的前向声明”。
警告
- 若需定义未初始化的非常量全局变量,不要使用
extern
,否则会被视为前向声明而非定义。 - 尽管可用
extern
将constexpr
变量设为外部链接,但无法将其作为constexpr
前向声明,因为编译器必须在编译期知晓其值。若值位于其他文件,则无法获得。
可将其前向声明为const
(运行时常量),但此做法意义有限。
函数前向声明无需 extern
编译器通过是否提供函数体区分定义与声明;变量则须用 extern
区分未初始化定义与前向声明,否则二者语法无法区分:
// 非常量
int g_x; // 定义(未初始化)
int g_x { 1 }; // 定义(带初始化)
extern int g_x; // 前向声明
// 常量
extern const int g_y { 1 }; // 定义(const 必须初始化)
extern const int g_y; // 前向声明
避免在非 const 已初始化全局变量上使用 extern
以下两行语义等价:
int g_x { 1 }; // 默认外部链接
extern int g_x { 1 }; // 显式 extern(可能触发编译器警告)
尽管后者合法,主流编译器会发出警告。若意图为前向声明,应去除初始化;若意图为定义,应去除 extern
。
最佳实践
- 仅对全局变量前向声明或 const/constexpr 全局变量定义使用
extern
; - 不要在非常量全局变量定义上使用
extern
。
快速小结
// 全局变量前向声明(无初始化)
extern int g_y; // 非常量
extern const int g_y; // const
extern constexpr int g_y; // 不允许:constexpr 无法前向声明
// 外部全局变量定义(无 extern)
int g_x; // 未初始化,默认零初始化
int g_x { 1 }; // 已初始化
// 外部 const/constexpr 全局变量定义(带 extern)
extern const int g_x { 2 }; // const
extern constexpr int g_x { 3 }; // constexpr
完整汇总见《作用域、存储期与链接小结》。
C++外部链接与变量前向声明测验
问题 1
简述变量的作用域、存储期与链接属性的区别。全局变量具备何种作用域、存储期与链接属性?