嵌入式编码规范实践要点(MISRA、华为与团队约定)
规范不是形式主义,而是把"踩过的坑"固化成纪律。本文从星球的规范资源与具体编码争论中,提炼可直接落地的实践要点。
一、可参考的规范体系
星球公开推荐过三份规范资料:华为 C 语言编程规范(完整 PDF)| Jack,华为C语言编码规范;BARR-C 嵌入式 C 编码标准(2018 版,兼容 MISRA C,中英对照)| 立芯嵌入式,嵌入式C编码规范,兼容MISRA C,中英对照;Google 代码风格指南(建议对照原版仓库阅读)| 立芯嵌入式,Google的代码风格指南,中英双语链接都在下面了,有能力的同学建议参考原版仓库。三者层次不同:华为规范面向大型团队的工程纪律,BARR-C/MISRA 面向嵌入式安全性与可移植性,Google 风格指南面向代码一致性与可读性。
二、类型选择:可移植性优先
- 用 uint8_t 还是 bool? 星球的结论是认同 CMSIS Driver 库用 uint8_t 的做法:bool 到 C99 才确定且各编译器支持不一,uint8_t 跨平台能力更强;bool 在 32 位平台上可能按 4 字节对齐反而浪费空间;uint8_t 能表达 0-255 多种状态码,API 返回值信息量更大、便于扩展。代价是需要约束——设计 API 时必须明确每个返回值对应的状态 | Jack,是否认同对于用uint8_t代替bool的规范。
- 位域慎用:位域的对齐与填充规则随平台/编译器而变。若代码有移植性要求,推荐用标准整型加位运算替代位域;只有确定平台与编译器唯一时才可用 | Jack,老师,位域该不该用?是否需要刻意考虑在不同的平台间或编译环境下所产生的问题。
这两条的共同逻辑:嵌入式代码的寿命超过它所在的平台,规范要为"搬家"预留余地。
三、防御性编程:Assert 与判空
- 用指针必须先判空,无论上游业务逻辑是否已经保证——这是硬性纪律而非可选优化 | Jack,为什么老师的代码里面在反复判断指针是否为空。
- Assert 与 if 的分工值得明确(Assert 拦开发期错误、if 拦运行期错误,两者不可互相替代)| Jack,今天同学问了一个很有意思的问题。
- 单元测试与故障注入是测试阶段暴露问题的手段;测试驱动的价值在于让不同水平的工程师在同一个质量基准上对齐 | Jack,UnityTest有什么用?。
四、接口与架构约束
- 函数指针收进结构体形成接口:把一组函数指针封装在结构体里,是在跟协作者"约定接口"——替换其中的实现不影响他人代码,增加成员也不破坏整体架构;嵌入式里架构变动意味着别人要跟着改一堆东西 | Jack,老师,有个问题是,为啥就一个函数指针都要写在一个结构体里面。
- 高内聚低耦合的判定:两个主体之间多条连接中可以合并的连接(如多处日志调用)应合并到一条,否则就是耦合过度的信号 | Jack,为什么这样的设计不合适,违背了高内聚,低耦合。
- 统一异常机制:系统集成后调用链变长,若每层都返回状态码,错误处理代码会重复冗余;大型架构通常由系统层统一管理各模块错误码,porting/wrapper 层因此不再逐层返回状态 | Jack,老师你好,我想问问为什么系统集成后的porting层和warpper层都不要返回值表。
- 实例化与初始化分离:inst 只挂载接口,init 负责内存申请与硬件使能,deinit 对称释放——这让模块(如低功耗场景下的传感器)可以安全地反复使能/失能,而控制块本身不必释放 | Jack,挂载inst:register和init的关系。
- 先 .h 后 .c:写面向对象式 C 程序先写头文件,本质是先做抽象、归纳事物的特点再实现 | Jack,写面向对象的程序时,要先写.h文件,后写.c文件。要对事物的本质进行抽象。
五、团队约定:环境与优化等级
规范不只约束代码,还约束工程环境:团队统一指定版本的开发工具链,旧版本一律卸载重装——环境不一致是排查成本最高的坑之一 | Jack,工作中,学习中,如果电脑上之前安装了对应的软件了怎么办。优化等级同样要作为团队约定固定下来(开发阶段建议 -O0),避免"低优化能跑、高优化出错"的玄学问题 | Jack,老师,我这优化等级和你们demo一样是3,为什么这队列接收进来的数据会变。
小结
一套可落地的嵌入式编码规范 = 成熟规范文本(华为/BARR-C/MISRA)+ 团队自己的约定(类型、判空、优化等级、环境版本)+ 接口纪律(结构体接口、统一错误码、inst/init 分离)。规范的目标不是限制自由,而是让问题在编码期暴露,而不是在客户现场。
来源笔记
- 华为C语言编码规范 | Jack
- 嵌入式C编码规范,兼容MISRA C,中英对照 | 立芯嵌入式
- Google的代码风格指南,中英双语链接都在下面了,有能力的同学建议参考原版仓库 | 立芯嵌入式
- 是否认同对于用uint8_t代替bool的规范 | Jack
- 老师,位域该不该用?是否需要刻意考虑在不同的平台间或编译环境下所产生的问题 | Jack
- 为什么老师的代码里面在反复判断指针是否为空 | Jack
- 今天同学问了一个很有意思的问题 | Jack
- UnityTest有什么用? | Jack
- 老师,有个问题是,为啥就一个函数指针都要写在一个结构体里面 | Jack
- 为什么这样的设计不合适,违背了高内聚,低耦合 | Jack
- 老师你好,我想问问为什么系统集成后的porting层和warpper层都不要返回值表 | Jack
- 挂载inst:register和init的关系 | Jack
- 写面向对象的程序时,要先写.h文件,后写.c文件。要对事物的本质进行抽象 | Jack
- 工作中,学习中,如果电脑上之前安装了对应的软件了怎么办 | Jack
- 老师,我这优化等级和你们demo一样是3,为什么这队列接收进来的数据会变 | Jack