7-7 外部链接与变量前向声明

在上一课(7.6——内部链接)中,我们讨论了内部链接如何将标识符的使用限制在单个文件内。本课将探讨外部链接的概念。

具有外部链接external linkage的标识符既可在定义文件中被访问使用,也可通过前向声明在其他代码文件中被访问使用。从这个意义上说,外部链接标识符真正实现了“全局性”——它们可在程序的任意位置被使用!

关键要点
具有外部链接的标识符对链接器可见。这使链接器能够完成两项工作:

  • 将一个翻译单元中使用的标识符与另一个翻译单元中的相应定义建立关联。
  • 消除内联标识符的重复,保留唯一的规范定义。内联变量与函数将在第7.9节——内联函数与变量中进行讨论。

函数默认具有外部链接

第2.8课——多代码文件程序中,你了解到可以在一个文件中调用另一个文件中定义的函数。这是因为函数默认具有外部链接。

若要在其他文件中调用函数,必须在所有需要使用该函数的文件中进行前向声明。前向声明告知编译器该函数的存在,链接器则负责将函数调用与实际函数定义进行关联。

示例如下:

a.cpp:

#include <iostream>

void sayHi() // this function has external linkage, and can be seen by other files
{
    std::cout << "Hi!\n";
}

main.cpp:

void sayHi(); // forward declaration for function sayHi, makes sayHi accessible in this file

int main()
{
    sayHi(); // call to function defined in another file, linker will connect this call to the function definition

    return 0;
}

上述程序输出:

image

在上例中,main.cpp 对 sayHi() 函数的向前声明使得 main.cpp 能访问 a.cpp 中定义的 sayHi() 函数。该向前声明满足了编译器的要求,链接器因此能够将函数调用与函数定义建立关联。

若 sayHi() 函数采用内部链接,链接器将无法将函数调用与函数定义关联,从而导致链接器报错。


具有外部链接的全局变量

具有外部链接的全局变量有时被称为外部变量external variables。要使全局变量成为外部变量(从而可被其他文件访问),我们可以使用 extern 关键字实现:

int g_x { 2 }; // non-constant globals are external by default (no need to use extern)

extern const int g_y { 3 }; // const globals can be defined as extern, making them external
extern constexpr int g_z { 3 }; // constexpr globals can be defined as extern, making them external (but this is pretty useless, see the warning in the next section)

int main()
{
    return 0;
}

非 const 全局变量默认具有外部作用域,因此无需显式标记 extern。


通过 extern 关键字进行变量前向声明

要实际使用在另一个文件中定义的外部全局变量,必须在任何其他需要使用该变量的文件中为该全局变量添加前向声明。对于变量,创建前向声明同样通过 extern 关键字实现(不带初始化值)。

以下是使用变量前向声明的示例:

main.cpp:

#include <iostream>

extern int g_x;       // this extern is a forward declaration of a variable named g_x that is defined somewhere else
extern const int g_y; // this extern is a forward declaration of a const variable named g_y that is defined somewhere else

int main()
{
    std::cout << g_x << ' ' << g_y << '\n'; // prints 2 3

    return 0;
}

以下是这些变量的定义:

a.cpp:

// global variable definitions
int g_x { 2 };              // non-constant globals have external linkage by default
extern const int g_y { 3 }; // this extern gives g_y external linkage

image

在上例中,a.cpp 和 main.cpp 都引用了同一个名为 g_x 的全局变量。因此,尽管 g_x 在 a.cpp 中被定义并初始化,我们仍能通过 g_x 的前向声明在 main.cpp 中使用其值。

请注意 extern 关键字在不同上下文中的含义存在差异。在某些场景中,extern 表示“赋予该变量外部链接”;而在其他场景中,extern 表示“这是对外部变量(定义于其他位置)的向前声明”。确实容易混淆,因此我们在第 7.12 课——作用域、存储期与链接总结中对所有用法进行了系统梳理。

警告
若需定义未初始化的非const全局变量,请勿使用extern关键字,否则C++会将其视为该变量的前向声明。

警告
尽管constexpr变量可通过extern关键字赋予外部链接,但无法作为constexpr进行前向声明。这是因为编译器需要在编译时获知 constexpr 变量的值。若该值定义在其他文件中,编译器无法获取该文件中定义的具体值。
不过,你可以将 constexpr 变量作为 const 进行前向声明,编译器会将其视为运行时常量。但这种做法并无实际意义。

请注意,函数的前向声明无需使用 extern 关键字——编译器能够根据函数是否提供主体来判断是定义新函数还是进行前向声明。而变量的前向声明则需要 extern 关键字,以区分未初始化的变量定义与变量前向声明(二者外观完全相同):

// non-constant
int g_x;        // variable definition (no initializer)
int g_x { 1 };  // variable definition (w/ initializer)
extern int g_x; // forward declaration (no initializer)

// constant
extern const int g_y { 1 }; // variable definition (const requires initializers)
extern const int g_y;       // forward declaration (no initializer)

避免在带初始化的非const全局变量上使用extern

以下两行语义等价:

int g_x { 1 };        // extern by default
extern int g_x { 1 }; // explicitly extern (may cause compiler warning)

然而,尽管后者在技术上是有效的,编译器仍可能对此语句发出警告。

还记得我们说过编译器有权对可疑情况发出诊断信息吗?此处正是典型案例。按惯例,当需要前向声明时,应将 extern 应用于非 const 变量。但添加初始化器会使该语句变成定义。编译器正在提示你存在异常情况。要修正此问题,可移除初始化表达式(若本意为前向声明),或移除 extern 关键字(若本意为定义)。

最佳实践
仅将 extern 用于全局变量的前向声明或 const 全局变量的定义。
不要将 extern 用于非 const 全局变量的定义(它们默认即为 extern)。


快速摘要

// Global variable forward declarations (extern w/ no initializer):
extern int g_y;                 // forward declaration for non-constant global variable
extern const int g_y;           // forward declaration for const global variable
extern constexpr int g_y;       // not allowed: constexpr variables can't be forward declared

// External global variable definitions (no extern)
int g_x;                        // defines non-initialized external global variable (zero initialized by default)
int g_x { 1 };                  // defines initialized external global variable

// External const global variable definitions (extern w/ initializer)
extern const int g_x { 2 };     // defines initialized const external global variable
extern constexpr int g_x { 3 }; // defines initialized constexpr external global variable

我们在第7.12节提供了全面的总结——作用域、存储期和链接总结。


测验时间

问题 #1

变量的作用域、存活期和链接类型有何区别?全局变量具有何种作用域、存储期和链接类型?

作用域(Scope)决定变量在何处可被访问。存储期(Duration)决定变量何时被创建和销毁。链接性(Linkage)决定变量能否被导出到其他文件。

全局变量具有全局作用域(又称文件作用域),这意味着从声明点到声明文件结尾均可访问该变量。

全局变量具有静态存续期,即程序启动时创建,程序结束时销毁。

全局变量可通过static和extern关键字分别实现内部或外部链接。
posted @ 2026-02-22 16:10  游翔  阅读(0)  评论(0)    收藏  举报