C 语言 static 完整讲解:分三大场景
static 是存储修饰符,作用分全局变量、局部变量、函数三种场景,核心两个能力:
- 改变内存存放区域;
- 限制作用域(文件隔离)。
一、场景 1:修饰局部变量(函数内部)
1. 普通局部变量(无 static)
c
运行
void func(void)
{
int a = 0; // 存栈Stack,函数调用时创建,退出直接销毁
a++;
printf("%d ", a);
}
调用多次输出:1 1 1 1
- 内存:栈;每次调用重新初始化为 0;函数结束销毁。
2. static 局部变量
c
运行
void func(void)
{
static int a = 0; // 存.bss/.data段,只初始化1次
a++;
printf("%d ", a);
}
调用多次输出:1 2 3 4
核心特性:
- 初始化仅执行 1 次:程序上电第一次进函数才赋值,后续调用保留上次值;
- 内存不在栈,在全局 RAM (.bss/.data):函数退出不会销毁,生命周期 = 整个程序运行;
- 作用域仅限当前函数 :外部其他函数无法访问变量
a; - 单片机优势:不占用栈空间,递归 / 循环不会栈溢出。
二、场景 2:修饰全局变量(函数外部)
1. 普通全局变量
c
运行
int g_val = 10; // 存.data段,整个工程所有.c文件都能extern访问
工程任意文件写 extern int g_val; 就能跨文件读写。
2. static 全局变量
c
运行
static int g_val = 10; // 存.data段,仅限当前.c文件使用
核心特性:
- 内存区域不变:仍在.data/.bss,程序全程存在;
- 文件作用域隔离 :其他
.c文件无法通过extern引用,杜绝跨文件重名冲突; - 单片机项目规范:所有模块内部全局缓冲区(NTC 数组、缓存)全部加 static。
三、场景 3:修饰函数
1. 普通函数
c
运行
void TestNtc(void)
{
}
整个工程任意文件都能直接调用。
2. static 函数
c
运行
static void TestNtc(void)
{
}
核心特性:
- 仅限当前.c 文件调用,外部文件无法引用;
- 模块化隔离:只给本文件内部使用的工具函数,全部加 static,防止全局函数名泛滥;
- 链接时不会导出符号,固件体积轻微减小。
四、static 内存分布汇总表
表格
| 写法 | 内存区域 | 生命周期 | 作用域 |
|---|---|---|---|
函数内 int a |
栈 Stack | 函数单次调用 | 仅当前函数 |
函数内 static int a |
.bss/.data | 整个程序运行 | 仅当前函数 |
文件外 int g_a |
.bss/.data | 整个程序运行 | 全工程所有文件 |
文件外 static int g_a |
.bss/.data | 整个程序运行 | 仅限当前.c 文件 |
普通函数 void func() |
代码段 Flash | 程序运行 | 全工程可调用 |
static void func() |
代码段 Flash | 程序运行 | 仅限当前.c 文件 |
五、结合 NTC 单片机代码实战例子
例 1:static 局部变量(滤波计数,保留上次值)
c
运行
uint16_t Ntc_GetAdc(void)
{
static uint16_t filter_buf[8]; // 静态数组,不占栈,保存历史采样
static uint8_t idx = 0;
uint16_t adc = ADC_Read();
filter_buf[idx++] = adc;
idx %= 8;
// 均值滤波计算...
}
不加 static 的话,filter_buf在栈,数组大了直接栈溢出。
例 2:static 全局表(NTC 码值表,仅 ntc.c 内部使用)
c
运行
// ntc.c 文件内,其他文件无法访问这个表
#ifdef NTC_R_10K
static const uint16_t ntc_code[] = {0x01C4,0x023B,...};
#elif defined(NTC_R_100K)
static const uint16_t ntc_code[] = {0xXXXX,...};
#endif
例 3:static 内部工具函数(仅 ntc.c 内部调用)
c
运行
// 查表转换温度,外部文件不能调用
static int Ntc_CodeToTemp(uint16_t adc)
{
// 二分查表逻辑
}
// 对外提供的接口,不加static,其他文件可调用
int Ntc_GetTemp(void)
{
uint16_t adc = Ntc_ReadAdc();
return Ntc_CodeToTemp(adc);
}
六、高频易错点
-
static const常量:- 全局
static const:存.rodata只读 Flash,不占 RAM; - 函数内
static const:同样存在只读区,不会每次进函数重复分配。
- 全局
-
static 局部变量初始化只一次: c
运行
static int a = 10; // 仅上电执行一次 a = 10; // 每次调用都会重新赋值,覆盖记忆值 -
栈溢出解决方案:大数组、缓冲区全部用
static修饰,转移到全局 RAM,不占用栈。 -
模块化规范:
- 模块私有变量 / 工具函数:全部加
static; - 对外接口、跨文件变量:不加 static。
- 模块私有变量 / 工具函数:全部加
总结
- 函数内 static 变量:保值、不占栈、生命周期全局;
- 全局 static 变量 /static 函数:锁死在当前文件,外部不可访问。