Files
STM32F4-Base/docs/门禁说明/coding-standard.md
T
wandering1 d64bbdc57f feat(wave): 故障录波数据面、接线与单通道回放页(软件闭环交付)
数据面(App/algo,纯逻辑,双端编译)
- wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask
  唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃
  兜底——下一次真故障无波可录比丢一段更贵
- wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带
  锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度
- 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式
- 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样
  纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉
- test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条
  自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合
- 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定)

接线(任务层)
- adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图),
  段头按最低有效位精确记账,不经浮点回算
- adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实;
  台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行
- defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到
  关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms
  补一条告警并记历史峰值(喂狗余量可核)
- Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留
  超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的,
  半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进
- Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到
  上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数;
  段文件字节布局零改动,三份合成段哈希逐字节不变
- protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体
  零改动

人机(160×160 单色屏)
- 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器
  得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30
- 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与
  垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、
  单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据
- ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、
  手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口
- 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有
  60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形
- 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变
- 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏
  22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、
  仪器用已在跑的单轮工作耗时探针,不新加埋点

台账与文档
- 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936
  字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名;
  散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建
  重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置)
- 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据);
  §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径
  风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」
- 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项
- 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划
  索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动)
- 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件
  形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记
  「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场
- 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行
  227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退

为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与
体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账
同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道

编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串
生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
2026-10-29 19:23:15 +08:00

9.7 KiB
Raw Blame History

coding-standard — 编码规范 V1.0 的可机械化子集

一句话:它只查规范里能用形状判定的 20 条(命名、宏、注释块、括号、禁用函数、goto、 递归、可变参数……)。规范里"名字要达意、函数要短、职责要清"这些它一条都管不了。 规范正文 = docs/代码规范/嵌入式C语言代码规范_V1.0.md。第 20 条(禁 C 库堆分配)出处不在规范正文 —— 规范 §6.1 的示例自己就写着 malloc/free(通用 C 模板遗留,已同日在示例下加批注), 它的出处是平台约束:MicroLIB 的 C 堆只有 512 B。

  • 配置:entry: python scripts/check_coding_standard.py、pass_filenames: true、 排除 test/、CubeMX 生成的 Src/、Inc/、第三方与 Lib/。

1. 它在查什么(逐条对应规范条款号)

规则号 内容 条款
CODE-RULE-01 .h 必须有 XXX_H 形式的保护宏,禁止 __ 前缀 2.3.1
CODE-RULE-02 文件开头必须有文件头注释(含 模块名称 / 作者) 5.2
CODE-RULE-03 #define 宏名全大写 + 下划线 + 数字 3.4.1
CODE-RULE-04 typedef 类型名以 _t 结尾 3.5.1
CODE-RULE-05 switch 必须有 default 6.3.2
CODE-RULE-06 禁用不安全函数 strcpy/strcat/sprintf/gets/scanf/strtok 7.5
CODE-RULE-07 禁止 goto 6.3.3
CODE-RULE-08 禁止 Tab 缩进(4 空格) 4.1.1
CODE-RULE-09 函数定义必须有函数注释(含「函数功能」) 5.3
CODE-RULE-10 函数名全小写(HAL_ 厂商回调名豁免) 3.3.1
CODE-RULE-11 禁止相对/绝对路径 include 2.3.5
CODE-RULE-12 禁止可变参数函数 7.6
CODE-RULE-13 禁止递归函数 7.7
CODE-RULE-15 文件作用域指针变量必须初始化为 NULL 6.1.4
CODE-RULE-16 文件作用域非 static 全局变量必须 g_ 前缀 3.2.2
CODE-RULE-17 非 static 函数必须在配对 .h 声明(无配对头则跳过) 2.2
CODE-RULE-18 禁止 Yoda 条件(字面量必须在运算符右侧) 6.3.1
CODE-RULE-19 函数定义左大括号与右括号同行 4.4.2
CODE-RULE-20 禁止 C 库堆分配 malloc/calloc/realloc/strdup/strndup/free/aligned_alloc 平台约束(非规范 V1.0 条款)

规则号里 CODE-RULE-14 是缺的:条款作废后编号不回收,留着是为了让历史文档里的"14"不指向别的东西。

2. 为什么要它

  • 这 19 条如果靠人看,评审成本极高且必然漏;机械化之后评审可以把注意力留给机器管不了的部分。
  • 有硬约束背景的两条特别值:禁止递归与禁止可变参数——本仓 DBG_* 每个调用点内联约 90 B Flash + 216 B 栈,栈与 Flash 都是红线资源;g_ 前缀和指针初始化则是最容易埋雷的静态状态。
  • CODE-RULE-20 有真事垫背(2026-10-23 落地):MicroLIB 的 C 堆由 startup_stm32f407xx.s 的 Heap_Size EQU 0x200 = 512 B,而它的踩坑方式是模拟器全对、真机必炸:配置引擎 2026-09-18 那次堆分配纠偏(4 处 KB 级 malloc 落在 C 堆上、真机返 NULL,见 docs/子系统设计/配置文件设计说明.md v1.11 记录)与 lftpd 的 malloc 隐患(docs/内存管理/RAM使用分析与优化方案.md §4.2.1)都是 上线后才发现的那类。宿主照不出来的缺陷,靠评审等于没管。

3. 触发面 / 看不见什么

  • 只看改动的 C/H 文件(pass_filenames: true);test/、Src/、Inc/(CubeMX 生成)、 第三方、Lib/ 不查。
  • 后果:同一份规范对 test/board/(编进固件的 6300+ 行)不生效,对 CubeMX 生成区也不生效。 test/ 的静态检查由 test-cppcheck 兜一半,形状规范没人管。
  • 逐文件判定:跨文件的事实(声明在 A、定义在 B、调用在 C)它只看到眼前这一个文件。
  • CODE-RULE-20 跑在掩码后的文本上(注释与字符串里写 malloc 不报);换来的代价是: 把分配藏进宏展开里它也看不见。
  • CODE-RULE-20 有一处点名豁免(simulator/sim/cfg_file_store_sim.c 的 malloc/free): 那是平台分配后端的宿主侧实现(MCU 侧同名后端用 pvPortMalloc),不是漏网。

4. 报错怎么读

Drivers/BSP/X/x.c:42 [CODE-RULE-09] 函数 foo_bar 缺少函数注释(应含「函数功能」)
  • 单独跑:& '.venv\Scripts\python.exe' scripts/check_coding_standard.py <文件...>。
  • 报的是形状,通常一眼能改;改不动的(例如厂商回调必须叫那个名字)走脚本里的显式豁免名单, 别在源码里塞空格绕开正则。

5. 好处

  • 形状统一,diff 干净;新人不必记住整份规范也能过门禁。
  • 规则号可追溯:报错直接给出"规则号 + 条款号",能查到出处。

6. 限制与盲区

  1. "有注释"≠"注释对":return-doc 类判据(见 bsp-err)与本门的 CODE-RULE-09 都只查 关键词在不在,写了"函数功能:见调用方"照样通过。
  2. 豁免靠形状:HAL_ 前缀直接放过函数名规则——任何以 HAL_ 开头的项目内函数也一并被放过。
  3. CODE-RULE-17 在没有配对 .h 时跳过:新写的 .c 忘了建头,就不会被这条抓到 (双端注册由 registration-check 管,声明完整性没人管)。
  4. 命名"达意"、函数长度、圈复杂度、职责边界:完全没有机械检查。
  5. test/ 与生成区整目录不查(§3),这三类目录的规范只靠人。
  6. 逐文件 + 正则形状:把违规写进字符串字面量、注释、宏展开里就看不见;反过来说,宏里真正的 违规代码也可能看不见。
  7. CODE-RULE-20 大小写敏感:这是特性也是盲区——pvPortMalloc/vPortFree 因此不误报, 但写成 Malloc( 就绕过了(同形后缀也一样:buf_free( 因词边界不成立而不算违规)。
  8. CODE-RULE-20 只管堆分配函数名:alloca 与变长数组(VLA)是栈上的坑,不归它; 分配不释放(泄漏)、释放后继续用(UAF)也不归它——本条只判"别碰 C 堆"这一件事。
  9. CODE-RULE-09 的回看窗口只有 25 行:脚本 scripts/check_coding_standard.py:194 取的是函数定义行 往前 25 行(window = lines[idx-26:idx-1]),在里面找「函数功能」关键词。于是函数头注释块一旦写 到 25 行以上,关键词就被挤出窗口——注释本身完全合规,也照样报「函数缺少函数注释块」。2026-10-29 一次真实触发(lead 复跑抓到 6 处、已改 3 个生产文件,全部是纯注释重排、无一行逻辑改动):录波 落盘道与 UI 道上四个带长口径注释的函数(wave_store_drain、s_log_record_edge、App/task/ui_task.c 的录波按键 helper、simulator/main.c 的合成喂拍 helper)各撞一到两处。写法上的规避:把长口径 另起一块放在函数注释块之前(两块分开),或把细节搬进函数体内——别让「函数功能」那一行离定义行超过 24 行。
  10. 不带文件参数就是空跑:脚本 :321 是逐 sys.argv[1:] 处理的,不给文件参数时一个字节都不查、 直接返回 0。手工复现这条门禁时把那个 RC=0 读成「过了」是错的——必须显式列出要查的路径(pre-commit 里由框架喂 staged 文件,所以钩子本身不会空跑)。同族坑见 docs/代码规范/pre-commit使用说明.md §2。

补录(判据细节,迁移自原手册):

  • 报错形态:L123 [CODE-RULE-03 3.4.1] 宏名须全大写+下划线: net_htonl(规则号与规范条款号一起给)。
  • CODE-RULE-11 细化过的判定形态(2026-10-11):禁的是用路径找文件——引号 include 带斜杠、 任意形态带 .. 或反斜杠、绝对路径或盘符。
  • Drivers/BSP/GD5F2GQ5UE/nand_ftl.* 也在排除清单里(dhara/FatFs 接口适配层,函数名与形态由 第三方接口决定,不该按我们的命名规范判它)。
  • 明确列为"不在检查范围"的语义类条款:函数长度与单一职责、参数数量、错误处理方式、被注释掉的代码。

7. 豁免与基线

  • 没有基线:硬拦截,新写的代码必须合规;因此放宽任何一条都会立刻降低约束,需谨慎。
  • 豁免方式只有两种:脚本里的显式豁免名单(按名字形状,如 HAL_ 回调)、或改代码。
  • CODE-RULE-20 的豁免是脚本里的 HEAP_ALLOC_ALLOWLIST,按文件路径 + 具体函数名双条件放行, 现仅一条:simulator/sim/cfg_file_store_sim.c → malloc/free(平台分配后端的宿主侧实现, 设计依据见 docs/子系统设计/配置文件设计说明.md 的 MCU 端/宿主端两段式后端)。 ⚠ 刻意不做目录级豁免:下次谁要 malloc 就得往这里加一行,评审看得见。

8. 改这个门禁本身

  • 新增规则前先在全库跑一次统计违规数;有存量的规则要么先清存量,要么走"基线只减不增" 的模式(参考 bsp-err、layer-deps),不要直接挂上硬拦截卡住所有人。
  • 规则作废不回收编号(见 CODE-RULE-14)。
  • 新增规则前先全库普查违规数(本节上一条已写)。CODE-RULE-20 落地时的普查结果:钩子范围内 C/H 共 151 个文件,命中只有 cfg_file_store_sim.c 那两行(即 §7 那条豁免)→ 直接硬拦截、 零基线;pre-commit run coding-standard --all-files 实测 Passed。自证:临时样例里 malloc/calloc/free 各报一处,pvPortMalloc/vPortFree/buf_alloc(、注释、字符串 全部不误报。