第4章 标识符与命名规则:长度、作用域与冲突规避
标识符命名这件事,看起来简单,但坑特别多。我见过不少项目,代码跑起来没问题,可一读代码就头疼——变量名短得看不懂,长得又记不住,还动不动就冲突。MISRA-C/C++ 在这块定了很多规矩,说白了就是为了让你写的代码能让人看懂、能安全运行。
这一章,我们聊聊标识符的长度、作用域限制、命名冲突怎么避免,以及哪些标识符绝对不能碰。
4.1 标识符长度:太短看不懂,太长记不住
MISRA 对标识符长度有明确要求。为什么?因为编译器对标识符的有效长度有限制。你想想看,如果两个变量名前31个字符都一样,只有第32个不同,有些编译器就会把它们当成同一个变量——这不出 bug 才怪。
| 作用域 | 最小有效长度 | 说明 |
|---|---|---|
| 内部作用域(函数内) | 31 字符 | 局部变量、函数参数等 |
| 外部作用域(全局) | 31 字符 | 全局变量、函数名、宏等 |
temperature_sensor_value 就比 tsv 好得多。
这里有个容易忽略的点:宏定义的标识符也要遵守长度规则。我见过有人写了个宏叫 MAXIMUM_BUFFER_SIZE_FOR_UART_TRANSMISSION,长度超过 40 个字符,结果在不同编译器下行为不一致。嗯,这种问题排查起来特别痛苦。
4.2 作用域限制:别让变量到处跑
MISRA 要求标识符的作用域要尽量小。这个原则其实很好理解——变量能见到的范围越小,出问题的概率就越低。
我个人习惯是:能用局部变量就不用全局变量。全局变量就像公共厕所,谁都能用,但谁都不愿意打扫。
作用域限制的核心规则:
- 函数内变量:只在函数内可见
- 文件内静态变量:只在当前 .c 文件可见
- 全局变量:整个程序可见(尽量少用)
举个例子,看看这个代码:
/* 不好的做法:全局变量满天飞 */
int g_counter; // 谁都能改,谁都能读
void increment(void) {
g_counter++;
}
/* 好的做法:用静态变量限制作用域 */
static int s_counter; // 只有本文件能访问
void increment(void) {
s_counter++;
}
我在项目中遇到过一个问题:两个模块都用了全局变量 status,结果一个模块改了值,另一个模块不知道,整个系统就乱了。从那以后,我坚决要求团队:全局变量必须加模块前缀,比如 uart_status、can_status。
4.3 命名冲突的避免:别让名字撞车
命名冲突是嵌入式开发里最常见的坑之一。尤其是当你把多个第三方库、中间件、驱动代码拼在一起的时候,冲突概率直线上升。
MISRA 给出了几条硬规则:
- 不要隐藏外层作用域的标识符——比如函数参数名和全局变量名相同
- 不要重复使用同一个标识符——即使在不同作用域也不行
- 宏定义不要和函数、变量重名
看个反面例子:
int count = 0; // 全局变量
void process(int count) { // 参数名和全局变量冲突!
count++; // 这里改的是参数,不是全局变量
// 想改全局变量?得用 ::count(C++)或者换个名字
}
这种代码,读起来特别容易误解。你想想看,维护的人看到 count++,第一反应是全局变量变了,结果其实没变——这种 bug 最难查。
task,结果和 FreeRTOS 的 TaskHandle_t 类型名冲突了。编译报错的时候我找了半天才反应过来——命名冲突有时候藏得很深。
4.4 保留标识符的使用禁令:这些名字碰不得
MISRA 明确禁止使用以下标识符:
| 类别 | 示例 | 原因 |
|---|---|---|
| 下划线 + 大写字母开头 | _RESERVED |
编译器/标准库保留 |
| 双下划线 | __reserved |
编译器内部使用 |
| 下划线 + 小写字母开头(文件作用域) | _local_var |
标准库保留 |
| 标准库函数/宏同名 | printf、NULL |
避免覆盖标准库行为 |
为什么这么严格?因为编译器、标准库、操作系统底层用了很多带下划线的名字。你如果用了 _Thread 或者 __FILE__,很可能和系统内部定义冲突,编译不过还是小事,运行时出诡异 bug 才真要命。
- 全局变量:
g_模块名_变量名(如g_uart_rx_buffer) - 静态变量:
s_变量名(如s_init_done) - 宏定义:全大写 + 下划线(如
BUFFER_SIZE_MAX) - 函数名:模块名_动作(如
uart_send_byte)
4.5 知识结构总览
下面这张图把本章的核心知识点串起来了。你可以把它当成一个检查清单——写代码的时候对照着看,基本不会出问题。
4.6 实战建议:建立团队的命名规范
光知道规则还不够,关键是要落地。我建议每个团队都建立一份命名规范文档,包含:
- 前缀规则:全局变量用
g_,静态变量用s_,模块内部函数用mod_ - 命名风格:统一用下划线分隔(snake_case)还是驼峰(camelCase)?我个人推荐嵌入式用 snake_case,因为和标准库风格一致
- 禁止列表:把常见的保留标识符列出来,贴在墙上
- 代码审查:每次 code review 都检查命名是否符合规范
一句话总结:标识符命名不是小事。长度够、作用域小、不冲突、不碰保留名——做到这四点,你的代码就已经比大多数项目强了。