extern 是什么?
extern 是 C 语言中的一个存储类型说明符(Storage Class Specifier),它的核心作用是声明一个变量或函数,而不是定义它。

- 声明:告诉编译器“这个名字存在,它的类型是这个,但我现在不分配内存空间给你”,编译器会相信你,并期望在链接阶段找到这个名字的实际定义。
- 定义:告诉编译器“这个名字存在,它的类型是这个,并且我现在要为你分配内存空间”,一个变量在一个程序中只能被定义一次。
extern 的作用是:“这个变量(或函数)是在别处定义的,我这里只是提前告诉编译器它的存在和类型。”
extern 变量的主要用途
extern 变量主要用于解决在多个源文件(.c 文件)之间共享全局变量的问题,在一个大型项目中,我们通常会将代码拆分成多个文件,extern 就是实现这种跨文件通信的关键。
单个文件中的 extern
我们先来看一个简单的例子,理解 extern 在单个文件中的作用。
场景:函数中使用全局变量
假设你有一个全局变量,但在它被定义之前,就想在某个函数里使用它。

// file: main.c
#include <stdio.h>
// 函数声明
void printGlobalVar();
int main() {
printGlobalVar(); // 在 globalVar 定义之前调用函数
return 0;
}
// 全局变量的定义
int globalVar = 100;
// 函数定义
void printGlobalVar() {
// 如果这里直接使用 globalVar,编译器会报错,
// 因为编译器从上到下扫描,还不知道 globalVar 是什么。
// 所以需要 extern 声明。
extern int globalVar; // 告诉编译器 globalVar 是一个 int 类型,在别处定义了
printf("The value of globalVar is: %d\n", globalVar);
}
解释:
int globalVar = 100;是 定义,它为globalVar分配了内存空间,并初始化为 100。extern int globalVar;是 声明,它只是告诉编译器“存在一个名为globalVar的int变量”,它不会分配新的内存空间。- 在
printGlobalVar函数中,由于函数定义在globalVar定义之后,编译器在编译printGlobalVar时已经“知道”了globalVar,这里的extern声明在技术上不是必需的,但它是一个很好的编程习惯,可以明确表达意图。
多个文件中的 extern(核心用法)
这是 extern 最常见和最重要的用法,假设我们有两个文件:main.c 和 utils.c。
场景:在 main.c 中使用 utils.c 中定义的全局变量
步骤 1:在 utils.c 中定义全局变量
这个文件是变量的“家”,这里是变量的实际定义。
// file: utils.c
#include <stdio.h>
// 全局变量的定义
// 注意:这里没有 extern
int sharedCounter = 0;
void incrementCounter() {
sharedCounter++;
printf("Counter incremented in utils.c. New value: %d\n", sharedCounter);
}
- 关键点:
int sharedCounter = 0;是一个完整的定义,编译器会为它在内存中分配空间。
步骤 2:在 main.c 中使用 extern 声明该变量
这个文件需要使用 sharedCounter,但它自己不定义它。
// file: main.c
#include <stdio.h>
// 声明在 utils.c 中定义的全局变量
// extern 告诉编译器:sharedCounter 是一个 int,
// 它的真实定义在别处(链接时会找到)。
extern int sharedCounter;
// 声明在 utils.c 中定义的函数
void incrementCounter();
int main() {
printf("Initial value of sharedCounter from main.c: %d\n", sharedCounter);
incrementCounter(); // 调用 utils.c 中的函数
incrementCounter(); // 再次调用
printf("Final value of sharedCounter from main.c: %d\n", sharedCounter);
return 0;
}
- 关键点:
extern int sharedCounter;是一个声明,不是定义,编译器看到这个声明后,就知道sharedCounter是一个整数,但不会在main.c中为它分配内存。- 当编译器将
main.c和utils.c分别编译成目标文件(main.o和utils.o)后,链接器(Linker)会将它们链接在一起,链接器会看到main.o中有一个对sharedCounter的引用,然后在utils.o中找到它的实际定义,并将两者关联起来。
编译和运行: 你需要将两个文件一起编译:
gcc main.c utils.c -o my_program
运行结果:
Initial value of sharedCounter from main.c: 0
Counter incremented in utils.c. New value: 1
Counter incremented in utils.c. New value: 2
Final value of sharedCounter from main.c: 2
这个例子完美地展示了 extern 如何让不同文件共享同一个全局变量。
extern 与 const 变量
当 extern 与 const 一起使用时,事情会变得稍微复杂一些,因为 const 变量的默认链接属性是内部链接(internal linkage),而普通全局变量是外部链接(external linkage)。
- 内部链接:变量只在当前文件内可见。
- 外部链接:变量可以在其他文件中通过
extern使用。
为了让一个 const 变量可以被其他文件使用,你必须显式地用 extern 声明它。
示例:
file: constants.h
// 头文件中只放声明,不放定义,是良好实践 extern const double PI;
file: constants.c
#include "constants.h" // 定义 const 变量 // 必须初始化 const double PI = 3.14159;
file: main.c
#include <stdio.h>
#include "constants.h" // 包含声明
int main() {
// 编译器知道 PI 是一个 const double,并且定义在 constants.c 中
printf("The value of PI is: %f\n", PI);
return 0;
}
如果不加 extern,const double PI; 在 constants.c 中只是一个内部链接的变量,main.c 将无法访问它。
extern 与 static 的对比
static 和 extern 在某种程度上是“对立”的,它们都影响变量的链接属性。
| 特性 | extern |
static (用于全局变量) |
|---|---|---|
| 作用 | 声明一个在其他地方定义的变量/函数 | 定义一个变量/函数 |
| 链接属性 | 外部链接 (External Linkage) | 内部链接 (Internal Linkage) |
| 可见范围 | 在整个程序的所有文件中可见(通过 extern 声明) |
仅在定义它的源文件内可见 |
| 内存分配 | 不分配内存,链接时关联到实际定义处 | 在程序的全局数据区分配内存 |
| 示例 | extern int x; |
static int y = 10; |
extern是“向外看”,让变量可以被外部文件访问。static是“向内锁”,将变量的作用域限制在当前文件内。
总结与最佳实践
extern用于声明:它告诉编译器一个变量或函数的存在和类型,但承诺其定义在别处。- 核心用途:实现跨文件的全局变量共享,在一个文件中定义,在另一个或多个文件中通过
extern声明来使用。 - 头文件的角色:
extern声明会放在头文件(.h文件)中,这样,任何需要使用这个全局变量的源文件(.c文件)只需要包含这个头文件即可,非常方便。- 定义:放在一个
.c文件中(globals.c)。 - 声明:放在对应的
.h文件中(globals.h)。
- 定义:放在一个
- 避免滥用全局变量:虽然
extern很方便,但过多使用全局变量会增加代码的耦合度,使程序难以维护和调试,在可能的情况下,优先使用函数参数和返回值来传递数据。 const变量:要让const全局变量具有外部链接,必须在声明时使用extern。
希望这个详细的解释能帮助你完全理解 C 语言中的 extern 变量!
