嵌入式 C 语言编码规范 V2
按嵌入式规范处理项目自有嵌入式 C 代码,并尽量控制改动范围,避免产生无意义的大 diff。
V2 默认工作模式
- 当任务是
review、审查、整改、提交前检查、看本次改动有没有问题这类增量检查时,优先使用 Git 变更范围,而不是全工程扫描。 - 增量审查默认只锁定本次变更涉及的自研
.c/.h文件,再结合函数级上下文、同模块头文件和直接关联定义做审查。 - 不要机械地只看 diff 行;要把 diff 当入口,再补读当前函数、必要的结构体、宏定义、枚举、资源释放路径和直接相关接口。
- 只有在用户明确要求全量扫描、建立初始基线、做大规模规范治理、批量重构或 CI 规则收敛时,才转为目录级或全工程审查。
- 若当前目录不是 Git 仓库,或仓库状态不足以确定基线,则退化为用户指定文件范围、目录范围或当前任务涉及文件的局部审查。
先判断审查范围
- 如果用户没有明确要求全量扫描,且当前工作区是 Git 仓库,优先通过 Git 确定增量范围。
- 优先审查以下几类范围:
- 工作区未提交变更
- 暂存区变更
- 当前分支相对基线分支的变更
- 只将变更集中的自研
.c、.h文件纳入默认审查范围;生成代码、第三方代码和构建产物默认排除。 - 若用户说“只看这次提交”“只看这次改动”“只审变更文件”,默认就是增量审查,不再主动扩展到全仓。
- 若变更文件很少,但影响到公共头文件、宏定义、共享结构体或资源生命周期,可按需补读直接相关文件;不要无边界扩散。
先判断代码归属
- 先判断目标文件属于项目自研代码、自动生成代码,还是第三方代码。
- 默认将
Drivers/、03_Os/、外部中间件,以及Core/下大多数 STM32Cube 生成文件视为自动生成或上游代码。 - 若目标属于第三方或生成代码,除非用户明确要求统一风格,否则只修改必要逻辑,尽量保留原有布局和上游风格。
- 若规范与上游风格冲突,优先保持局部一致性,并在最终回复里说明这是例外处理。
先消解文档里的歧义
- 把原始规范文档视为“以
.c实现文件为中心”的规范:所有 MUST 规则默认对.c生效;只有在确实修改接口时,才把命名、注释、宏和接口相关规则同步应用到.h。 - 当原文摘要表、正文和示例互相冲突时,优先级按以下顺序处理:
- 安全性和正确性规则
- 正文详细说明与具体示例
- 仓库内稳定的既有约定
- 摘要表中的简写结论
- 在项目自有代码中新建标识符时,默认采用以下归一命名规则:
- 文件名与函数名:
snake_case - 局部变量与参数:
lowerCamelCase - 文件级或全局可变状态:
g_+lowerCamelCase - 宏、枚举值、标签:
UPPER_SNAKE_CASE typedef后的抽象类型名:PascalCase- 不要为了追求风格统一去大面积重命名稳定公共 API、生成代码符号、厂商接口或已有调用链,除非用户明确要求。
应用格式与文件组织规则
- 只使用空格缩进,每一级 4 个空格,禁止插入 TAB。
- 行宽不超过 81 列;URL、命令行、
#include、#error等确有必要时可例外。 - 使用 K&R 大括号风格:
- 函数的左大括号独占一行
- 控制语句的左大括号放在语句行末
- 右大括号通常独占一行,除非后面紧跟
else、else if或do { } while (0)的尾部while if、else、for、while、do、switch一律显式使用大括号,即使只有一条语句或空循环体。switch中的case和default相对switch缩进一级,并显式写出break、return或说明型 fallthrough。- 保持一行一条语句,不要把多条动作塞进一行,也不要用晦涩写法隐藏副作用。
- 若项目使用 CMake 或 Makefile 构建,头文件搜索路径应按“底层到顶层”排序,以减少无效路径匹配并改善编译速度。推荐顺序:芯片与架构层(CMSIS、设备头)→ HAL/驱动层 → OS 与第三方中间件 → BSP/平台组件 → 应用层。
- 新建或大幅重写
.c文件时,优先采用以下组织顺序: - 文件头注释
#include- 宏与常量
- 类型定义
- 文件级
static变量 - 内部声明
- 内部
static函数 - 对外函数
- 新建
.c或.h文件时必须使用以下文件头注释模板(替换对应字段):/****************************************************************************** * @file <filename> * * @par dependencies * * @author <Author Name> * * @brief <Brief description> * * @version V1.0 <YYYY-M-D> * * @note 1 tab == 4 spaces! *****************************************************************************/ - 所有函数声明,和函数定义需要包含注释,必须使用以下注释模板:
/** * @brief * * @param[in] : * * @param[out] : * * @return * * */ - 函数内部注释,需要遵循下面意见,注释统一写在代码前一行,必须使用以下注释模板:
/**
- This is a comment **/
- 注释默认用英文,重点说明设计意图、边界条件、硬件约束和非显然取舍,不要机械复述代码本身,每个函数都必须要有函数内注释。
应用安全性、可维护性与可移植性规则
- 让每个函数尽量只做一件事。可行时将函数控制在 60 行非空非注释代码以内,嵌套层级不超过 4 层。
- 对任何可能失败的 API,调用后立即检查返回值,包括项目接口、HAL、OS、内存分配、I/O 等。
- 同一模块内部尽量保持一致的返回值策略,不要混用多套错误语义。
- 对所有外部输入做显式校验,包括 API 入参、文件数据、网络报文、IPC 数据、环境信息,以及跨信任边界的共享全局状态。
- 不要用
assert校验外部输入;assert仅用于内部不变量。 - 对动态内存、锁、句柄、队列、信号量等资源,确保所有退出路径都能正确释放;资源较多时优先使用
cleanup:收口。 - 优先使用函数或
static inline替代函数式宏;若必须使用宏,必须完整加括号,多语句宏必须写成do { ... } while (0)。 - 尽量减少全局状态;如果符号只在当前文件使用,就声明为
static。对文件级或全局可变状态使用g_前缀。 - 若文件级可变状态可能被多线程或中断上下文访问,必须明确同步策略,如互斥锁、原子、关中断或单线程约束。
- 指针运算优先使用
uintptr_t或字节指针;不要通过uint32_t传递或截断指针。 - 长度、大小和索引优先使用
size_t;若必须缩窄到 32 位,先做显式边界检查。 - 生产代码优先走项目统一日志接口,不要随意保留
printf;同时避免打印敏感数据。 - 调试钩子、临时日志和测试代码不要混入发布路径;如必须保留,使用编译开关保护。
审查与整改时的优先级
- 先处理正确性、安全性、资源生命周期、并发和可移植性问题,再处理纯风格问题。
- 做增量审查时,先判断本次修改是否引入新的安全性或正确性风险,再考虑是否需要顺手规范化局部旧代码。
- 重点关注以下问题:
- 未检查返回值
- 未校验外部长度或范围
- 宏副作用与括号缺失
- 控制语句缺少大括号
- 指针截断
size_t缩窄- 内存泄漏、重复释放、释放非法对象
- 本应是
static的新增全局状态 - 除非用户明确要求,否则不要为了“看起来更整齐”而整文件重排或全量格式化。
V2 Git 增量审查指引
- 在 Git 仓库中,优先使用以下思路确定审查范围:
- 工作区未提交:
git status --short+git diff --name-only - 暂存区:
git diff --cached --name-only - 分支差异:
git diff --name-only <base>...HEAD - 变更过滤时优先保留项目自研模块(按项目实际目录结构识别,如应用层、服务层、平台层、BSP、工具库等自研目录)。
- 默认排除:
Drivers/03_Os/- 外部中间件
Core/下大多数生成代码- 构建产物、日志和临时文件
- 审查输出要聚焦本次变更引入的问题;历史遗留问题可简短提示,但不要让它们淹没本次改动的 findings。
- 若用户明确要求“顺带给出历史问题”或“建立基线”,再额外列出非本次改动问题。
- 若 Git 不可用,明确说明已退化为局部范围审查,并说明当前使用的范围选择依据。
需要更详细规则时再加载参考文档
- 做大规模 review、制定 CI 门禁、补
.clang-format、.clang-tidy、cppcheck规则时,再读取references/embedded-c-rules-zh.md。 - 需要把 Git 增量审查流程讲清楚、给出范围选择示例或说明为何不做全仓扫描时,再读取
references/git-incremental-review-zh.md。 - 构建静态检查配置时,优先从参考文档中的示例派生,不要随意发明与嵌入式规范无关的新规则。