3. 基础规则解读(一):环境、翻译与代码整洁
各位同学,今天咱们正式开始啃MISRA C的规则条文。说实话,刚接触MISRA的人,很容易被那几百条规则吓住。我当年也一样,心想这玩意儿要是全背下来,头发得掉光。
但干久了你会发现,规则背后都是有血泪教训的。今天这节,咱们先看三组最基础的规则——环境与翻译、未使用代码与注释、字符集与三字符组。这些规则看似简单,但踩坑的人真不少。
3.1 规则1.1 - 1.4:环境与翻译
这组规则讲的是C程序的翻译环境。说白了,就是编译器怎么把你的代码变成机器码。MISRA对这块有严格要求,因为很多未定义行为就藏在这里。
规则1.1:所有代码必须符合C标准
这条听起来像废话,但实际项目里,我见过太多人用编译器扩展。比如用__attribute__((packed))来搞结构体对齐,或者用#pragma关警告。MISRA要求你严格遵守C99标准,不能用那些“方言”。
__attribute__((aligned))来优化内存访问,结果换到ARMCC编译,结构体布局全乱了。从那以后,我要求团队所有代码必须通过-std=c99 -pedantic编译。
规则1.2:不能依赖未定义或未指定行为
这条是MISRA的核心精神。C标准里有很多“实现定义”和“未定义”行为,比如有符号整数溢出、求值顺序、移位操作等。MISRA要求你写代码时,不能依赖这些行为。
| 行为类型 | 例子 | MISRA要求 |
|---|---|---|
| 未定义行为 | i = i++ + ++i; | 禁止 |
| 未指定行为 | f(a(), b()); 参数求值顺序 | 禁止依赖 |
| 实现定义行为 | int 的位数 | 必须明确文档化 |
规则1.3:翻译单元不能包含多个定义
这条是关于“一次定义规则”的。同一个标识符,在同一个翻译单元里只能定义一次。嗯,这其实C标准本身就要求了,但MISRA把它明确写出来,防止有人用#include搞出重复定义。
规则1.4:不能使用内嵌汇编
这条争议很大。很多嵌入式工程师觉得,不用汇编怎么写底层驱动?但MISRA认为,汇编代码无法被静态分析工具检查,容易引入不可移植的代码。
.s文件里,用C函数封装。然后在MISRA检查时,把这个文件加入豁免列表。我之前的项目就是这么干的,既满足了功能需求,又通过了合规审计。
3.2 规则2.1 - 2.4:未使用代码与注释
这组规则看着简单,但实际项目里,很多代码腐烂就是从“未使用代码”开始的。你想想看,一个项目维护了五六年,里面有多少死代码?
规则2.1:不能有未使用的代码
这里的“未使用”包括:未调用的函数、未使用的变量、不可达的代码路径。MISRA要求你把这些清理干净。
/* 违反规则2.1的例子 */
static int unused_var = 42; /* 从未使用 */
static void dead_function(void) /* 从未调用 */
{
int x = 0;
return;
x = 1; /* 不可达代码 */
}
规则2.2:不能有死代码
死代码和未使用代码有点区别。死代码是那些永远不会被执行到的语句,比如if(0){...}或者while(0){...}。我见过有人用if(0)来“注释”掉代码块,这在MISRA里是不允许的。
#if 0 ... #endif和if(0){...}。问原来的人,说是“留着以后可能用”。结果呢?三年了,没人敢删。最后我们花了两周时间,把所有死代码清理掉,代码量减少了30%,编译时间也快了。
规则2.3:注释不能嵌套
这条规则针对的是/* */风格的注释。C标准不允许注释嵌套,但有些编译器会“宽容”处理。MISRA要求你严格遵守标准,不要用嵌套注释。
/* 这是合法的注释 */
/* 这是 /* 非法的嵌套注释 */ 编译器会报错 */
规则2.4:注释不能包含代码
这条规则的意思是,你不能用注释来“隐藏”代码。比如:
/* 错误做法:注释里藏着代码 */
/*
int old_function(void)
{
return 0;
}
*/
正确的做法是,要么删掉,要么用版本控制。我习惯用Git管理代码,旧版本都在仓库里,不需要用注释来“存档”。
3.3 规则3.1 - 3.2:字符集与三字符组
这组规则是关于字符编码的。你可能觉得这没什么,但我在国际项目里见过因为字符集问题导致的编译失败,那叫一个头疼。
规则3.1:只能使用基本源字符集
MISRA要求你只使用C标准定义的基本源字符集。说白了,就是ASCII里的那些字符。不能用中文变量名、不能用全角符号、不能用特殊Unicode字符。
| 允许的字符 | 不允许的字符 |
|---|---|
| A-Z, a-z, 0-9 | 中文、日文、韩文等 |
| 空格、制表符 | 全角空格、不间断空格 |
_ { } [ ] ( ) 等标点 | 智能引号、长破折号等 |
规则3.2:不能使用三字符组
三字符组是C标准里一个历史遗留问题。它用三个字符来表示一个符号,比如??=表示#,??(表示[。MISRA明确禁止使用三字符组。
/* 三字符组示例(违反规则3.2) */
??=include <stdio.h> /* 等价于 #include <stdio.h> */
int arr??(10??); /* 等价于 int arr[10]; */
为什么MISRA要禁止这个?因为三字符组在字符串里也会被替换,容易引发意想不到的错误。比如:
printf("What??!\n"); /* 这里的 ??! 会被替换成 | */
我有个同事就因为这个bug查了一整天。他写了个printf("Are you sure???");,结果输出变成了Are you sure|。嗯,从那以后,我们团队在代码审查时都会专门检查三字符组。
3.4 本章知识结构图
下面这张图总结了本章的核心规则和它们之间的关系。你可以把它当作一个快速参考。
3.5 实战建议
好了,规则讲完了。最后给几点实战建议:
- 工具自动化:别指望人工检查这些规则。用PC-Lint、Coverity或者SonarQube,配置好MISRA规则集,让工具帮你扫。
- 代码审查清单:把本章的规则做成审查清单。每次Code Review时,先过一遍清单,再开始看逻辑。
- 持续集成:在CI流水线里加入MISRA检查。我习惯在每次提交代码后,自动跑静态分析,不合格的直接打回。
- 团队培训:规则不是用来“背”的,是用来“理解”的。我每周会抽半小时,和团队一起过一条规则,结合实际案例讨论。