第4章:变量作用域前缀——g_、s_、m_与无前缀的江湖规矩
说实话,变量命名这件事,我见过太多团队在这上面栽跟头了。
有一次,一个同事调试了整整三天,最后发现是全局变量和局部变量重名了。编译器没报错,但逻辑全乱了。嗯,从那以后,我们团队就严格执行这套前缀规则。
4.1 全局变量:g_前缀
全局变量,说白了就是整个程序都能访问的变量。它的生命周期贯穿整个程序运行,谁都能改,谁都能读。
我个人习惯,全局变量必须加 g_ 前缀。为什么?
- 一眼识别:看到
g_就知道这变量是全局的,修改时要三思 - 避免冲突:局部变量和全局变量同名时,编译器会优先使用局部变量,这坑我踩过
- 代码审查:review 时看到
g_前缀,会格外注意它的使用范围
正确示例:
// 全局变量定义
int g_system_tick = 0;
uint32_t g_adc_value = 0;
char g_device_name[32] = "sensor_01";
// 使用全局变量
void timer_isr(void) {
g_system_tick++;
}
我曾经犯过的错:
在一个多线程项目中,我定义了一个全局变量 count,没加 g_ 前缀。结果某个线程里也定义了一个局部变量 count,两个变量互相干扰,数据全乱了。调试了两天才发现,从那以后我再也不敢省略 g_ 前缀了。
4.2 静态变量:s_前缀
静态变量分两种:文件内静态和函数内静态。它们的作用域都有限制,但生命周期是全局的。
我建议,静态变量统一加 s_ 前缀。你想想看,这样写代码的人一眼就知道:这个变量只在当前文件或当前函数内有效。
| 静态变量类型 | 作用域 | 生命周期 | 命名示例 |
|---|---|---|---|
| 文件内静态 | 当前文件 | 程序运行期间 | s_file_status |
| 函数内静态 | 当前函数 | 程序运行期间 | s_call_count |
实用技巧:
我在驱动开发中经常用静态变量来保存硬件状态。比如一个 UART 驱动,用 s_uart_tx_busy 来表示发送是否忙。这样其他文件想改也改不了,安全又清晰。
// 文件内静态变量
static int s_init_done = 0;
void module_init(void) {
if (!s_init_done) {
// 初始化代码
s_init_done = 1;
}
}
// 函数内静态变量
int get_call_count(void) {
static int s_call_count = 0;
return s_call_count++;
}
4.3 局部变量:不加前缀
局部变量,就是函数内部定义的临时变量。它的作用域只在当前函数内,生命周期随函数调用结束而结束。
说白了,局部变量不需要前缀。为什么?因为它的影响范围太小了,加了前缀反而显得啰嗦。
- 简洁明了:
int i;比int l_i;清爽多了 - 临时性:局部变量用完就丢,不需要长期维护
- 可读性:函数内部代码本来就短,变量名短一点反而好读
正确写法:
void process_data(uint8_t *buffer, int len) {
int i; // 循环变量,不加前缀
int sum = 0; // 临时变量,不加前缀
uint8_t temp; // 临时变量,不加前缀
for (i = 0; i < len; i++) {
temp = buffer[i];
sum += temp;
}
}
注意:
局部变量虽然不加前缀,但名字也不能太随意。我见过有人写 int a, b, c, d;,这种代码过两天自己都看不懂。局部变量也要有语义,比如 index、counter、checksum 这样的名字。
4.4 模块内部变量:m_前缀
模块内部变量,这个是我个人特别推崇的。它介于全局和局部之间,只在当前模块(通常是一个 .c 文件)内使用,但又不是静态的。
为什么需要 m_ 前缀?
- 模块封装:告诉其他开发者,这个变量是模块内部使用的,别乱动
- 代码分层:看到
m_就知道这是模块级变量,不是全局的 - 便于重构:想改模块内部实现时,只关注
m_开头的变量就行
// sensor_module.c
// 模块内部变量
int m_sensor_status = 0;
float m_temperature = 25.0f;
uint32_t m_last_read_time = 0;
void sensor_init(void) {
m_sensor_status = 1;
m_temperature = 25.0f;
m_last_read_time = 0;
}
int sensor_read(float *temp) {
if (!m_sensor_status) {
return -1;
}
// 读取传感器
*temp = m_temperature;
m_last_read_time = get_tick();
return 0;
}
我的经验:
在 RTOS 项目中,我经常用 m_ 前缀来管理任务相关的变量。比如一个温度采集任务,用 m_temp_buffer、m_sample_count 这样的变量,其他任务想访问?对不起,请通过接口函数。这样模块间的耦合度大大降低。
4.5 避坑指南
好了,说了这么多规则,我来总结几个实际开发中容易踩的坑:
我曾经踩过的坑:
- 全局变量不加 g_:在一个 10 万行的项目中,全局变量和局部变量重名,编译器不报错,但逻辑全乱。排查了整整两天。
- 静态变量忘记加 s_:写了一个中断服务函数,里面用了静态变量保存状态。结果另一个文件也定义了一个同名变量,两个变量互相覆盖,中断响应全乱了。
- 模块变量和全局变量混淆:一个模块的
m_变量被另一个模块直接访问,导致数据不一致。后来强制要求所有模块间通信必须通过接口函数。 - 局部变量名字太随意:写过
int a, b, c, d, e;的代码,一个月后自己都看不懂。现在局部变量至少要有语义,比如index、count、flag。
4.6 总结
这套前缀规则,说白了就是让代码自己会说话。看到 g_ 知道是全局,看到 s_ 知道是静态,看到 m_ 知道是模块内部,没前缀的就是局部变量。
我个人觉得,命名规范这东西,越早统一越好。等代码写到几万行再想改,那真是欲哭无泪。你想想看,是不是这个理?
一句话记住:
全局加 g_,静态加 s_,模块加 m_,局部不加。简单粗暴,但管用。