C 作用域与生命周期
更新时间:2026-08-26。本文是
languages/c/主题入门层第 22 篇,函数主题收官。作用域管"在哪能用",生命周期管"活多久"。搞清这两件事,之前"函数里变量外面就没了""返回局部变量地址是坑"的疑问就都串起来了。
本文要回答的问题
- 局部变量和全局变量差在哪?
static变量和普通局部变量有什么不同?- 同名变量嵌套时会怎样?
一、两个维度:作用域 vs 生命周期
| 维度 | 含义 | 例 |
|---|---|---|
| 作用域 | 名字在哪些代码里可见 | 块作用域、文件作用域 |
| 生命周期 | 存储存在的时间 | 自动(栈)、静态、动态(堆) |
一个变量可以"作用域很小但活得很久",也可以"看得到但马上死"——两个维度独立。
二、局部变量:块作用域 + 自动生命周期
c
void f(void) {
int a = 1; // a 从这开始可见
{
int b = 2; // b 只在这个内层块可见
printf("%d %d\n", a, b); // 能看到 a 和 b
}
// printf("%d\n", b); // 错!b 已离开作用域
printf("%d\n", a); // 没问题
}- 作用域:从声明处到所在
{}块结束; - 生命周期:进入块时分配(栈),出块时销毁;
- 局部变量每次进块重新创建,初值不确定(除非初始化)。
三、全局变量:文件作用域 + 静态生命周期
c
#include <stdio.h>
int counter = 0; // 全局变量:文件作用域,程序启动即存在
void bump(void) {
counter++; // 任何函数都能访问
}
int main(void) {
bump();
bump();
printf("%d\n", counter); // 2
return 0;
}- 作用域:从声明处到文件末尾(所有函数都能用);
- 生命周期:程序启动创建、程序结束销毁;
- 全局变量自动初始化为 0(局部变量不会!)。
但要克制:全局变量让函数之间的依赖隐式化,一个函数改了它,别处就"莫名其妙变了",排错很难。工程惯例是"尽量少用全局变量",需要共享状态时优先通过参数/返回值传递。多文件共享全局变量的正规方式是用 extern 声明(见第四节)。
四、static:延长生命周期,不扩大作用域
static 用在局部变量上,语义大变:
c
void counter(void) {
static int calls = 0; // 只初始化一次,函数退出不销毁
calls++;
printf("第 %d 次调用\n", calls);
}
int main(void) {
counter(); // 第 1 次调用
counter(); // 第 2 次调用
return 0;
}| 变量类型 | 生命周期 | 作用域 | 初始化 |
|---|---|---|---|
| 普通局部 | 函数调用期间 | 所在块 | 每次重新初始化 |
static 局部 | 整个程序 | 所在块(不变) | 只初始化一次 |
| 全局 | 整个程序 | 所在文件 | 自动为 0 |
static 局部变量 = "作用域还是局部的,但活得跟全局一样久"。适合:计数器、缓存、惰性初始化的单例。
static 还能修饰函数和全局变量:限制为文件内部可见(不导出符号),这是模块化的手段,见高手层多文件工程与模块化。
五、变量隐藏(shadowing):内层盖住外层
c
int x = 10; // 全局 x
void f(void) {
int x = 5; // 局部 x 把全局 x "盖住"了
printf("%d\n", x); // 5:这里用的是局部 x
}内层作用域的同名变量会隐藏外层。这是合法的 C,但极易让人读错——工程上避免同名变量嵌套。真要访问被盖住的全局变量,用 extern 或干脆改名字。
六、extern:引用别的文件的全局变量
c
// global.c
int shared = 42; // 定义
// other.c
extern int shared; // 声明:别的文件定义的,我引用
void print_shared(void) {
printf("%d\n", shared);
}extern 声明"这个变量在别处定义,我用它"。与头文件配合时:定义放一个 .c,声明(extern)放头文件,其他文件 include 即可。注意 extern 是声明,不能带初值(带初值就是定义了)。
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 局部变量忘初始化 | 垃圾值 | 声明即初始化 |
| 全局变量滥用 | 状态不可控 | 少用,用参数传递 |
| 返回局部变量地址 | 悬垂指针 | 见返回值篇 |
| 同名变量嵌套 | 读错变量 | 避免 shadowing |
| static 变量猜初始值 | 行为不符预期 | 记住 static 只初始化一次 |
八、与本站主线衔接
- 作用域/生命周期在内存里的落点(栈段、数据段、BSS),见专家层内存布局与 ABI;
static/extern与符号链接的关系,见专家层编译、链接与 ELF;- 动态生命周期(malloc/free)是下一篇大主题,见内存管理入门。
一句话总结
作用域管"在哪可见"(块/文件),生命周期管"活多久"(自动/静态/动态);局部变量出块即毁、全局变量程序级存活且自动清零、static 局部"活得久但只见得窄"——三张表记熟,变量的世界就理清了。