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_count、g_ 前缀,一眼就能区分。
5.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以看到局部变量和全局变量在作用域、生命周期、存储位置上的区别,以及同名变量遮蔽的发生条件。
5.6 总结与建议
好了,咱们把这一章的核心点捋一捋:
- 局部变量:函数内部定义,生命周期短,作用域小,不初始化就是垃圾值
- 全局变量:函数外部定义,生命周期长,作用域大,不初始化自动为0
- 遮蔽规则:同名时局部优先,想访问全局?换个名字或者用指针
我个人建议:能用局部变量就别用全局变量。全局变量就像公共厕所——谁都能用,但谁都不愿意打扫。如果非要用,记得加前缀、加注释、加保护。
嗯,这一章就到这儿。记住这些规则,写代码时心里就有底了。