5、局部变量与全局变量:生命周期与作用域,同名变量的遮蔽规则

好,咱们今天聊点实在的。变量这东西,说白了就是程序里存数据的小盒子。但盒子放哪儿、什么时候被回收、能不能被别的地方看到——这些规则,我当年刚入行时可是踩过不少坑。

你想想看,一个变量,你在函数里定义的和在函数外定义的,能一样吗?当然不一样。这就好比你在自己房间里放的东西,和放在小区公共大厅里的东西,管理规则完全不同。

5.1 局部变量:函数里的“临时工”

局部变量,就是在函数内部定义的变量。它的生命周期,从函数被调用时开始,到函数返回时结束。说白了,就是“用完即走”。

核心特征:

  • 作用域:仅限于定义它的那个函数内部
  • 生命周期:函数调用期间存在,调用结束自动销毁
  • 存储位置:栈区(stack)
  • 默认初始值:未初始化时是随机值(垃圾值)
#include <stdio.h>

void func() {
    int local_var = 10;  // 局部变量
    printf("局部变量: %d\n", local_var);
}  // 函数结束,local_var 被销毁

int main() {
    func();
    // printf("%d", local_var);  // 编译错误!这里访问不到 local_var
    return 0;
}

我个人习惯,在函数里定义变量时,一定会初始化。为什么?因为局部变量不初始化,里面存的是栈上残留的垃圾值。我曾经在调试一个通信协议栈时,发现某个标志位莫名其妙是0xFF,查了两天才发现——局部变量没初始化,栈上正好残留了之前中断处理函数的数据。嗯,从那以后,我写局部变量必初始化。

小技巧:局部变量尽量靠近使用它的地方定义。不要像老式C代码那样,把所有变量都堆在函数开头。这样既清晰,又能减少不必要的内存占用。

5.2 全局变量:程序里的“公共财产”

全局变量定义在函数外面,所有函数都能访问它。它的生命周期从程序启动开始,到程序结束才结束。说白了,就是“从一而终”。

核心特征:

  • 作用域:整个源文件(如果加了 extern,可以跨文件)
  • 生命周期:程序启动到程序结束
  • 存储位置:数据段(data segment)或 BSS 段
  • 默认初始值:未显式初始化时自动赋 0
#include <stdio.h>

int global_count = 0;  // 全局变量

void increment() {
    global_count++;  // 任何函数都能修改它
}

int main() {
    increment();
    increment();
    printf("全局计数: %d\n", global_count);  // 输出 2
    return 0;
}

全局变量好用吗?好用。但滥用全局变量,是很多嵌入式项目后期维护的噩梦。我见过一个项目,全局变量用了上百个,函数之间全靠全局变量传数据。结果呢?改一个地方,不知道会影响多少个函数,调试起来跟破案一样。

警告:全局变量在多线程或中断环境下是“共享资源”,必须加保护机制(如互斥锁、关中断)。否则,你可能会遇到“明明赋值了,读出来却是旧值”的诡异问题。我曾经在中断服务函数里直接修改了一个全局标志,主循环里轮询它,结果因为编译器优化,主循环一直读到寄存器里的缓存值——嗯,加了 volatile 才解决。

5.3 生命周期与作用域的对比

这两个概念容易混淆。我简单解释一下:

  • 作用域:代码的哪些部分能“看见”这个变量
  • 生命周期:变量在内存里存在多长时间

举个例子:你在一个函数里定义的局部变量,作用域只在这个函数里,生命周期也只有函数执行期间。而全局变量,作用域是整个文件,生命周期是程序全程。

特性 局部变量 全局变量
作用域 函数内部 整个源文件(可扩展)
生命周期 函数调用期间 程序运行全程
存储位置 栈区 数据段 / BSS 段
默认初始值 随机值(垃圾值) 0
访问范围 仅定义它的函数 所有函数(需注意 extern)
典型问题 未初始化导致野值 多任务竞争、命名冲突

5.4 同名变量的遮蔽规则

这个规则很有意思。当局部变量和全局变量同名时,会发生什么?

答案是:局部变量会“遮蔽”全局变量。在函数内部,你访问的是局部变量,全局变量被“藏”起来了。

#include <stdio.h>

int value = 100;  // 全局变量

void func() {
    int value = 200;  // 局部变量,与全局变量同名
    printf("函数内部 value = %d\n", value);  // 输出 200
}

int main() {
    func();
    printf("函数外部 value = %d\n", value);  // 输出 100
    return 0;
}

为什么会这样?C语言的作用域规则是:内层作用域优先于外层作用域。函数内部形成了一个新的作用域,它“挡住”了外部的同名变量。

那如果我真的想在函数内部访问被遮蔽的全局变量呢?可以用 ::value(C++ 支持)或者干脆别用同名变量。但在纯 C 里,没有直接的办法——你只能通过指针或者换个变量名。

避坑指南:我曾经接手过一个项目,全局变量叫 count,某个函数里也定义了一个局部变量叫 count。结果函数里本意是想累加全局的 count,但因为遮蔽,实际上一直在改局部变量。全局 count 纹丝不动,bug 藏了两周才被发现。所以我的建议是:全局变量加前缀,比如 g_countg_ 前缀,一眼就能区分。

5.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以看到局部变量和全局变量在作用域、生命周期、存储位置上的区别,以及同名变量遮蔽的发生条件。

变量作用域与生命周期知识体系 局部变量 全局变量 作用域:函数内部 生命周期:函数调用期间 存储位置:栈区 默认初始值:随机值 作用域:整个源文件 生命周期:程序全程 存储位置:数据段/BSS段 默认初始值:0 同名变量遮蔽规则:内层作用域优先于外层作用域

5.6 总结与建议

好了,咱们把这一章的核心点捋一捋:

  • 局部变量:函数内部定义,生命周期短,作用域小,不初始化就是垃圾值
  • 全局变量:函数外部定义,生命周期长,作用域大,不初始化自动为0
  • 遮蔽规则:同名时局部优先,想访问全局?换个名字或者用指针

我个人建议:能用局部变量就别用全局变量。全局变量就像公共厕所——谁都能用,但谁都不愿意打扫。如果非要用,记得加前缀、加注释、加保护。

嗯,这一章就到这儿。记住这些规则,写代码时心里就有底了。


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