d64bbdc57f
数据面(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 条仍全过
9.7 KiB
9.7 KiB
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/子系统设计/配置文件设计说明.mdv1.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. 限制与盲区
- "有注释"≠"注释对":
return-doc类判据(见bsp-err)与本门的 CODE-RULE-09 都只查 关键词在不在,写了"函数功能:见调用方"照样通过。 - 豁免靠形状:
HAL_前缀直接放过函数名规则——任何以HAL_开头的项目内函数也一并被放过。 - CODE-RULE-17 在没有配对
.h时跳过:新写的.c忘了建头,就不会被这条抓到 (双端注册由registration-check管,声明完整性没人管)。 - 命名"达意"、函数长度、圈复杂度、职责边界:完全没有机械检查。
test/与生成区整目录不查(§3),这三类目录的规范只靠人。- 逐文件 + 正则形状:把违规写进字符串字面量、注释、宏展开里就看不见;反过来说,宏里真正的 违规代码也可能看不见。
- CODE-RULE-20 大小写敏感:这是特性也是盲区——
pvPortMalloc/vPortFree因此不误报, 但写成Malloc(就绕过了(同形后缀也一样:buf_free(因词边界不成立而不算违规)。 - CODE-RULE-20 只管堆分配函数名:
alloca与变长数组(VLA)是栈上的坑,不归它; 分配不释放(泄漏)、释放后继续用(UAF)也不归它——本条只判"别碰 C 堆"这一件事。 - 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 行。 - 不带文件参数就是空跑:脚本
: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(、注释、字符串 全部不误报。