车规级编码规范MISRA C,入门时嗤之以鼻,出问题复盘后,才发现真香。
2018 年刚进汽车行业时,做网络测试,CAN、CANoe、CAPL、UDS 都会了,那时候学得快,也容易高估自己。
学完 OSEK NM 后,我自己手搓了一个协议栈,后来开源了,拿了不少 star,当时我觉得,代码能跑,基本就说明方向是对的。
后面被不少朋友拿去移植,经常偶发奇怪问题,这类问题最折磨人,复现困难,难以下手,后来有个朋友提醒了我,让我试试MISRA C扫描。
我也是恍然大悟,忘了还有这么个强大的代码质量工具可以用。
很多朋友第一次接触 MISRA C,会嫌它烦,规则多,限制多,很多过去写顺手的代码,现在突然不让这么写了,会很难受,可你只要做过量产项目,就会慢慢明白,MISRA 防的是平时不暴露的问题,但一旦进了量产,样本量一多,各种神奇的问题频繁发生,让你抓耳挠腮。
C 语言贴底层、效率高、控制力强,所以它一直活在 MCU、驱动、控制器这些关键位置上,但问题同样明显,标准里留了太多未定义行为、未指定行为、实现定义行为。
消费电子开发,这类问题只是一次偶发崩溃,重启一下也许就过去了,可在车上不行,一个signed 和 unsigned 的混算,一个移位超范围,一个空指针解引用,一个 switch 漏了 default,一个没有检查返回值的调用,都可能在不同编译器、不同芯片、不同优化等级下表现出完全不一样的结果。
最麻烦的是,它们不一定次次出问题,而是等条件凑齐时给你来一下,这样的问题最容易被漏测,最后在量产的阶段收账。
MISRA 的价值,就在这里,逼你把意图、边界和风险都写清楚。
MISRA 的规则分成 Mandatory、Required、Advisory 三类。
Mandatory 是高压线,Required 原则上必须守,要偏离就必须给出正式理由并纳入流程,Advisory 长期影响维护成本和团队协作效率。很多人不重视 Advisory,其实时间久了,团队之间的差距就体现在这个地方。
很多人学 C 语言,停留在语法层面,觉得编译能过就行,但到了工程现场,出问题的常常不是语法,而是表达式语义,boolean、enum、signed、unsigned、floating,在语言层面能拼起来,不代表工程层面就该这么拼,混合运算、窄化赋值、结果提升,看起来都是小动作,实际上最容易埋雷。
看几个常见规则,你会更容易理解 MISRA C的思路。
14.4 要求控制表达式必须明确是布尔值,是为了避免 if 条件里藏模糊语义,15.6 要求 if、for、while 一律带大括号,预防低级事故,16.4 要求 switch 必须有 default,逼你把兜底逻辑交代清楚,21.3 限制 malloc、free,防止内存碎片化。
你单独拆开看每一条,都不复杂,把这些要求合起来看,全是系统最容易失控的地方。
后来我参与域控制器研发,接触异构 SoC、Linux 应用开发,慢慢开始带团队,角色变了,对 MISRA 的理解也跟着变了,以前关心的是某条规则怎么改过去,现在关心的这套东西怎么才能真正变成团队的日常动作。
如果 MISRA 只活在文档里,而不是融入开发活动,那软件准出前,你一定会补作业补到吐血。
我的建议是,静态分析要前置,不要等联调、系统测试甚至交付前才去做全量检查,那时候代价已经很高了。
Code Review 也不能只看功能,类型、控制流、资源管理、返回值处理、显式转换、默认分支,这些规范项都应该进入评审清单,否则评审做得再认真,也只是把需求过了一遍,没有真正把风险压住。
还有偏差管理,底层寄存器访问、硬件地址映射、启动阶段内存池初始化,这类场景确实可能需要合理偏离规则,但偏离不是一句这里特殊就能过去,必须有记录、有理由、有审批、能追溯。
在我看来,MISRA是车规嵌入式研发的必用规则,把风险挡在量产之前,大家是做工程的,做工程就该遵守规则。

夜雨聆风