45、全局变量与 extern:如何跨文件使用全局变量?
说实话,全局变量这个话题,很多初学者觉得简单——不就是定义在外面嘛。但真正到了多文件工程里,翻车的人可不少。我见过太多人因为 extern 用错了,编译死活过不去,最后发现是声明和定义搞混了。
今天咱们就把这个事彻底讲清楚。
什么是全局变量?
全局变量,就是定义在所有函数外面的变量。它的生命周期是整个程序运行期间,作用域是从定义位置开始,到文件末尾。
// file1.c
#include <stdio.h>
int g_counter = 0; // 全局变量
void increment(void) {
g_counter++;
}
这段代码里,g_counter 就是一个全局变量。任何函数都能访问它、修改它。嗯,这里要注意——正因为谁都能改,所以容易出问题。我在项目中遇到过因为全局变量被意外修改,排查了整整两天的 bug。
跨文件访问的核心:extern
一个工程通常有多个 .c 文件。如果我想在 file2.c 里使用 file1.c 中定义的 g_counter,该怎么办?
答案就是 extern 关键字。
extern 的作用是告诉编译器:这个变量在别的文件里定义了,你先别报错,链接的时候再去找它。
// file2.c
#include <stdio.h>
extern int g_counter; // 声明,不是定义
void print_counter(void) {
printf("Counter = %d\n", g_counter);
}
你看,extern int g_counter; 这行代码只是声明,并没有分配内存。真正的内存分配在 file1.c 里的 int g_counter = 0; 那里。
关键区别:
- 定义:分配内存,可以初始化。如
int g_counter = 0; - 声明:不分配内存,只是告诉编译器有这个变量。如
extern int g_counter;
常见的坑:重复定义
我曾经见过一个新手,在两个 .c 文件里都写了 int g_counter = 0;,然后链接时报错:multiple definition of 'g_counter'。
为什么会这样?因为每个 .c 文件单独编译时,都定义了一个全局变量。链接器发现有两个同名的全局变量,就懵了。
正确的做法是:一个定义,多处声明。
| 文件 | 内容 | 说明 |
|---|---|---|
| file1.c | int g_counter = 0; |
定义,分配内存 |
| file2.c | extern int g_counter; |
声明,引用外部定义 |
| file3.c | extern int g_counter; |
声明,引用外部定义 |
最佳实践:头文件里放 extern 声明
我个人习惯的做法是:在头文件里放 extern 声明,在对应的 .c 文件里放定义。这样所有需要访问该变量的文件,只要包含头文件就行了。
// global.h
#ifndef GLOBAL_H
#define GLOBAL_H
extern int g_counter; // 声明
#endif
// global.c
#include "global.h"
int g_counter = 0; // 定义
// main.c
#include "global.h"
int main(void) {
g_counter = 10;
return 0;
}
这样做的好处是:
- 所有声明集中在一处,修改方便
- 避免在多个文件里重复写 extern
- 头文件自文档化,一看就知道有哪些全局变量
小技巧: 如果你在头文件里不小心写了 int g_counter = 0;(没有 extern),然后这个头文件被两个 .c 文件包含,就会导致重复定义。所以头文件里只放 extern 声明,千万别放定义。
static 全局变量:文件内部的“全局”
有时候你希望某个全局变量只在当前文件内使用,不想被其他文件访问。这时候可以用 static 关键字。
// file1.c
static int g_internal = 0; // 只在本文件可见
void set_internal(int val) {
g_internal = val;
}
加上 static 后,其他文件即使写 extern int g_internal; 也链接不到。这其实就是信息隐藏,是模块化设计的好习惯。
我在做嵌入式项目时,经常把硬件相关的寄存器映射变量声明为 static,只通过接口函数暴露出去。这样别人用起来不容易出错。
避坑指南
我曾经踩过的坑:
- 忘记加 extern:在 file2.c 里直接写
int g_counter;,结果链接时发现重复定义。记住,想引用外部变量,必须加 extern。 - extern 和定义混在一起:在头文件里写
extern int g_counter = 0;,这其实是定义,不是声明。extern 后面不能跟初始化值。 - 全局变量命名冲突:两个模块都定义了同名的全局变量,链接时出错。建议用模块前缀,比如
g_timer_count、g_uart_baud。
知识体系图
下面这张图帮你理清全局变量跨文件使用的核心逻辑:
总结一下
全局变量跨文件使用,说白了就三句话:
- 一个 .c 文件里定义(分配内存)
- 头文件里用 extern 声明(告诉编译器有这个东西)
- 其他文件包含头文件(就能用了)
你想想看,是不是很简单?但越简单的东西,越容易在细节上翻车。我建议你写代码时,养成一个习惯:全局变量尽量少用,如果非用不可,一定要用 extern 声明在头文件里,并且加上模块前缀。
嗯,今天就聊到这里。记住,全局变量是把双刃剑,用好了方便,用不好就是灾难。
公众号:蓝海资料掘金营,微信deep3321