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_countg_uart_baud

知识体系图

下面这张图帮你理清全局变量跨文件使用的核心逻辑:

全局变量跨文件使用核心逻辑 定义(.c 文件) int g_var = 0; 声明(头文件) extern int g_var; 使用(其他 .c 文件) #include "header.h" 编译时 链接时 ⚠️ 注意事项 • 一个变量只能定义一次,但可以声明多次 • 头文件中只放 extern 声明,不放定义 ✅ 最佳实践 • 使用模块前缀命名全局变量(如 g_timer_count) • 尽量用 static 限制作用域,只暴露必要的接口

总结一下

全局变量跨文件使用,说白了就三句话:

  • 一个 .c 文件里定义(分配内存)
  • 头文件里用 extern 声明(告诉编译器有这个东西)
  • 其他文件包含头文件(就能用了)

你想想看,是不是很简单?但越简单的东西,越容易在细节上翻车。我建议你写代码时,养成一个习惯:全局变量尽量少用,如果非用不可,一定要用 extern 声明在头文件里,并且加上模块前缀。

嗯,今天就聊到这里。记住,全局变量是把双刃剑,用好了方便,用不好就是灾难。


公众号:蓝海资料掘金营,微信deep3321