Files
STM32F4-Base/docs/代码规范/cppcheck使用说明.md
T
wandering1 c63bd029a9 feat(门禁): 新增 skill-size 钩子——SKILL.md 不得超插件写侧上限 65536
dsh-mnemon 的 MAX_SKILL_BYTES = 65536 只在写侧校验,加载侧从不检查,
所以正文越界的技能照常加载、却永远生成不出草案:同日两次卡死
(83194 字节与 72601 字节)都是这个形态,复位台账无效,只能瘦身。
故把上限机械化,越界与悬空指针在提交期就拦下。

- scripts/check_skill_size.py:纯标准库(裸 python 也能跑,故不解析
  .venv);FAIL >65536、FAIL reference/ 指针悬空、FAIL UTF-8 BOM、
  WARN ≥49152(75%——实测一次「采纳」把主文从 49821 顶到 62806);
  总是全树扫,只拿 reference/x.md 触发的提交正是漏网场景;
  --root 可复指到用户级 ~/.dsh/skills;--selftest 五形态(干净 0 /
  临界 0+WARN / 超限 1 / 悬空+BOM 1 / 根不存在 2)全过
- .pre-commit-config.yaml 加 skill-size,files: ^\.dsh/skills/
- 新增 docs/门禁说明/skill-size.md,含盲区:拦不住插件自身的采纳写盘、
  用户级技能目录不在触发面、不解析 frontmatter、不查 U+FFFD
- 计数同步 51 -> 52(实跑 48 -> 49,索引 28 -> 29 篇):AGENTS.md 与
  门禁索引,另 7 处文档旧计数改写、CI 指南带日期实测行只加注

本笔先落地,技能正文的渐进展开拆分在下一笔。
2026-10-29 15:02:23 +08:00

12 KiB
Raw Blame History

cppcheck C 静态分析 — 使用说明

本文档是 docs/代码规范/pre-commit使用说明.md 的配套详解,聚焦 cppcheck 钩子的检查规则、配置参数与误报抑制方法。 pre-commit 的整体部署架构与其他钩子规则见 docs/代码规范/pre-commit使用说明.md。


1. 概述

cppcheck 是开源 C/C++ 静态分析工具,不运行代码,通过语法分析和数据流推理发现潜在缺陷。

工作原理:逐个读取 C 文件 → 分析语法/数据流/控制流 → 发现问题则报错。

与 pre-commit 的关系:cppcheck / test-cppcheck 两根钩子都带 stages: [manual]——git commit 阶段不跑,由远端 CI(.gitea/workflows/ci.yml 的「手动阶段门禁」步,逐根 --hook-stage manual)与 scripts/ci_local.py 的 manual 段调用 cppcheck 命令行;任一已启用类别发现问题即失败(--error-exitcode=1 对 warning/performance/portability/style 全部生效)。

日常速度

  • 提交期:不跑(stages: [manual]),一次带代码的 git commit 只跑 49 根快钩子
  • 全库扫描(约 20 s;2026-10-28 实测 cppcheck 20.5 s、test-cppcheck 38.1 s):手工复跑 & '.venv\Scripts\python.exe' -m pre_commit run --all-files --hook-stage manual <hook-id>——一次只能给一个 id(pre_commit run a b c 实测报 unrecognized arguments),故 cppcheck / test-cppcheck 各跑一次

2. 检查类别

本项目开启的类别

类别 含义 典型问题
warning 通用警告 变量未使用、除零风险、空指针解引用、unreachable code
performance 性能问题 不必要的值拷贝、字符串重复查找、低效的函数调用
portability 可移植性问题 不同平台行为不一致的代码、隐式类型转换
style 代码风格 变量作用域过大、冗余赋值、恒假条件、自赋值等可疑写法(详见第 3 节)

不开启的常见类别

类别 为什么不开
error 严重错误(数组越界、内存泄漏),cppcheck 默认已包含,不需要显式加
information 信息类(未解析的头文件等),无实际价值

3. style 类别具体检查什么(cppcheck 2.21.0)

style 是"风格/质量"级别——报告的问题不一定是 bug,但绝大多数是冗余代码、死代码、可疑写法、可以写得更安全/更清晰的代码。下面按主题列出本项目(纯 C)实际会命中的规则(--enable=style 已开启):

① 冗余与死代码(redundant & dead code)— 最值得修

规则 检查什么 反例
redundantAssignment 变量赋值后被新值覆盖、旧值从未被读取 ret = OK; ret = f();(首个赋值无意义)
unreadVariable 变量赋值后从未被读取 s = -1; 随后循环里被覆盖、从未用到
redundantCondition 短路求值使某条件恒真 if (a && a > 5)(a 非零时前半已恒真)
duplicateCondition if/else 分支条件重复 if (x){} else if (x){}
duplicateBreak 连续重复 break break; break;
duplicateExpression / duplicateExpressionTernary 运算符两侧表达式相同 a = a && b、x = x ? 1 : 2
unreachableCode return 之后不会执行的代码 return 0; foo();
unassignedVariable 变量声明后从未赋值 int x; use(x);
unusedLabel 从未被 goto 的标签 label: 无对应 goto

② 恒真/恒假条件(always-true / always-false)— 常藏逻辑 Bug

规则 检查什么 反例
knownConditionTrueFalse 依据数据流推断条件恒真/恒假 while (n>=365){ ... if (n<360) break; }
knownArgument 参数固定时函数结果可推断 strlen("") 恒为 0
knownPointerToBool 已知指针当布尔用 p 恒非空时 if (p)
compareValueOutOfTypeRangeError 与类型范围外的值比较 uint16_t x; if (x > 65535) 恒假
unsignedLessThanZero 无符号 < 0 恒假 无符号 n 的 if (n < 0)
unsignedPositive 无符号 >= 0 / > -1 恒真 if (n >= 0)
pointerLessThanZero / pointerPositive 指针 < 0 / > 0 if (p < 0)

③ const 化与作用域(const & scope)

规则 检查什么 备注
constVariable / constVariablePointer 变量/指针可加 const 防误改 已全库启用并修复
constParameter / constParameterPointer / constParameterCallback 参数可加 const 回调相关两条已抑制(HAL 签名不可改)
variableScope 变量作用域可缩小(声明移入实际使用的最内层块) 已全库修复
functionStatic / staticFunction 函数可加 static 限制可见性 本库函数多被外部引用,命中少

④ 可疑写法(suspicious patterns)— 疑似笔误

规则 检查什么
clarifyCalculation 位运算与逻辑运算易混(| vs ||、& vs &&)
clarifyCondition / assignmentInCondition 条件里出现赋值(疑似 = 误写 ==)、链式比较
bitwiseOnBoolean 对布尔量做位运算
selfAssignment 自赋值 x = x
oppositeExpression 同一条件内互相矛盾的表达式
moduloofone x % 1(恒为 0)
mismatchingBitAnd 位运算不匹配
suspiciousFloatingPointCast 浮点强转整型(可能丢精度)
incrementboolean 对布尔量自增
shadowVariable / shadowArgument / shadowFunction 局部变量遮蔽外层变量/参数/函数
arrayIndexThenCheck 数组下标先使用、后越界检查
truncLongCastAssignment / truncLongCastReturn long 强转导致截断
funcArgNamesDifferent 函数参数名与声明不一致
nanInArithmeticExpression 算术表达式中出现 NaN
ignoredReturnErrorCode 忽略带错误码的返回值
unpreciseMathCall 调用不精确的数学函数
returnNonBoolInBooleanFunction 布尔函数返回非布尔值
multiCondition 可疑的多条件组合
duplicateAssignExpression 同一表达式赋给多个变量
redundantBitwiseOperationInSwitch / redundantAssignInSwitch switch 中冗余位运算/赋值

⑤ 不适用本 C 工程的规则(C++ 专属,自动跳过)

catchExceptionByValue、cstyleCast、missingOverride、noConstructor、noExplicitConstructor、operatorEqRetRefThis、unusedScopedObject、virtualCallInConstructor、useStlAlgorithm、constParameterReference 等(异常/类/STL 相关,纯 C 代码不会命中)。

说明:

  • 上表规则均出自 cppcheck 2.21.0 --errorlist 中 severity=style 的真实清单。
  • 部分规则(如 duplicateBranch、suspiciousSemicolon、checkConst)需要额外 --inconclusive 才会激活,为减少误报未开启。
  • unusedFunction、unusedStructMember、useStlAlgorithm、noExplicitConstructor 等已在 --suppress 中抑制,原因见第 4.3 节「抑制规则说明」。
  • --error-exitcode=1 对所有已启用级别(warning/performance/portability/style)生效:任何一级发现问题都会让门禁失败(在 CI / ci_local.py 的 manual 段拦下)。style 级别的问题若确属误报,用行内抑制(见第 5 节)。

4. 配置

4.1 配置参数

--std=c99                    # 按 C99 标准检查(Keil 工程常用)
--error-exitcode=1           # 发现问题返回非零退出码(pre-commit 据此判该钩子失败)
--inline-suppr               # 启用行内抑制注释(// cppcheck-suppress,默认关闭)
--quiet                      # 只报问题,不显示扫描进度

# --- 必须抑制(误报/不可修改) ---
--suppress=missingInclude    # HAL/CMSIS 路径在 Keil 工程配置里,cppcheck 不知道
--suppress=invalidPrintfArgType_sint  # printf 的 %d 传 uint32_t —— 实际安全
--suppress=invalidPrintfArgType_uint  # printf 的 %u 传 int —— 实际安全
--suppress=constParameterPointer     # HAL 回调签名固定(如 SPI_HandleTypeDef*),不能加 const
--suppress=constParameterCallback    # 回调函数指针类型限制,不能改签名
--suppress=unusedFunction            # 第三方库回调/弱符号函数被误报为未使用
--suppress=unusedStructMember        # HAL 寄存器位域成员未显式使用
--suppress=noExplicitConstructor     # C++ 兼容,不影响纯 C
--suppress=useStlAlgorithm           # C++ STL 相关,不影响纯 C

# --- 头文件路径 ---
-I Inc -I App -I Drivers/BSP
-I Drivers/STM32F4xx_HAL_Driver/Inc -I Drivers/CMSIS/Include
-I Middlewares/Third_Party/FreeRTOS/Source/include

注意:以上为逻辑参数列表。在 .pre-commit-config.yaml 中书写时必须遵守第 4.2 节的 YAML 引号规则。

4.2 ⚠️ YAML 参数引号陷阱(曾导致钩子完全失效)

.pre-commit-config.yaml 的 args: 使用 YAML 流序列 [a, b, c] 语法,其中逗号是元素分隔符、空格不产生新元素。曾出现两个致命错误,导致 cppcheck 钩子空转("Passed" 但从未真正检查):

错误写法 实际被解析成 后果
[--enable=warning,performance,portability,style] --enable=warning + 3 个假文件参数 performance/portability/style 只启用 warning,style/performance/portability 全部失效
[-I Inc -I App -I Drivers/BSP] 单个参数 "-I Inc -I App -I Drivers/BSP" 所有 include 路径失效(指向一个不存在的目录名)

正确写法:含逗号或空格的参数必须加引号,-I 与路径必须分成两个元素:

args: ['--enable=warning,performance,portability,style', '--error-exitcode=1',
       '-I', 'Inc', '-I', 'App']

排查方法:怀疑参数被拆坏时,在 PATH 前置一个同名 shim(如 cppcheck.cmd 把 %* 写日志),跑 pre-commit run cppcheck --files <某文件> 查看实际执行的完整命令行。

4.3 抑制规则说明

抑制规则 原因 是否永久
constParameterPointer HAL 回调签名由驱动框架定义,用户不能改 永久
constParameterCallback 同上,回调函数指针类型受限 永久
unusedFunction CubeMX 生成的弱符号、第三方库回调被误报 永久
unusedStructMember HAL 寄存器位域按位操作,cppcheck 不理解 永久
redundantAssignment HAL 清标志宏(如 __HAL_SPI_CLEAR_OVRFLAG)读寄存器覆盖旧值,cppcheck 不理解"读即目的" 永久

4.4 扫描范围

通过 types: [c] + exclude 控制:

检查:App/、Src/、Inc/、Drivers/BSP/ 下的 .c 文件

不检查(第三方库,问题不是我们的):

Drivers/CMSIS/              # ARM CMSIS
Drivers/STM32F4xx_HAL_Driver/  # ST HAL
Drivers/BSP/UI/             # LVGL UI 层
Drivers/BSP/NET/lftpd/      # LwIP FTP
Drivers/BSP/dhara/          # NAND FTL
Lib/                        # LVGL 库
Middlewares/                # FreeRTOS
MDK-ARM/                    # Keil 工程文件
test/                       # 测试代码

5. 误报抑制方法

5.1 行内抑制(// cppcheck-suppress)

确属误报、又不想全局 --suppress 掉整条规则时,在报错行的上一行加注释:

// cppcheck-suppress knownConditionTrueFalse ; 说明原因(分号后文字可选)
if (day_count < diy) break;

要点:

  • 需要 --inline-suppr 开关(已在配置中启用),否则注释无效
  • 注释必须紧贴报错报告行的上一行(cppcheck 报告行可能不是你以为的那行,以输出为准)
  • 检查 ID 后跟的 (...) 括号文字会被当作 suppression attribute 解析而报错,说明文字用 ; 后跟

5.2 全局抑制(--suppress)

某条规则在整个工程中都是噪音/不适用时,在 .pre-commit-config.yaml 的 args 中添加 --suppress=规则名(同样遵守 4.2 节引号规则),现有清单见 4.1 节。