C++外部链接与变量前向声明详解(含extern用法与代码示例)

什么是外部链接?

在上一课(内部链接)中,我们讨论了内部链接将标识符的作用范围限制在单个源文件。本课将探讨外部链接的概念。

具有外部链接的标识符不仅在其定义文件可见,也可通过前向声明在其他源文件中使用。从这个意义上说,外部链接的标识符是真正的“全局”标识符,可在整个程序的任何位置使用。

关键洞察
具有外部链接的标识符对链接器可见,使得链接器能够完成两项工作:

  1. 将某翻译单元中使用的标识符连接到另一翻译单元中的正确定义;
  2. 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.cppa.cpp 引用的是同一个 g_x。虽然 g_xa.cpp 内定义并初始化,但通过前向声明,main.cpp 也能使用其值。

注意:关键字 extern 在不同上下文含义不同:

  • 在定义处:extern 表示“具有外部链接”;
  • 在前向声明处:extern 表示“这是一个外部变量的前向声明”。

警告

  1. 若需定义未初始化的非常量全局变量,不要使用 extern,否则会被视为前向声明而非定义。
  2. 尽管可用 externconstexpr 变量设为外部链接,但无法将其作为 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

简述变量的作用域、存储期与链接属性的区别。全局变量具备何种作用域、存储期与链接属性?

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"