Files
STM32F4-Base/docs/代码规范/cppcheck使用说明.md
T
wandering1 a6cdebde62 文档体系重组与 D-7 一致性门禁落地,清理芯片手册孤儿图片
- D-6 目录规范化:docs 根目录散落文档归入 UI设计/子系统设计/架构设计/审查记录/
  环境与部署/网络 等子目录,去掉目录名空格,重命名 嵌入式C语言代码规范_V1.0.md
- D-7 三档一致性门禁:新增 scripts/check_doc_links.py(链接存在性)、
  scripts/gen_task_table.py(FreeRTOS 任务表数值)、scripts/check_mem_numbers.py
  (内存/Flash 体积),均支持 --check/--write/--diff,pre-commit 钩子同步注册
- D-5 文档索引:新建 docs/README.md,新建 docs/文档规范.md 约束目录命名与链接写法
- D-1 芯片手册瘦身:删除 1069 张无引用孤儿图片(77.06 MB),
  docs/芯片手册 120 MB → 41 MB,已引用图片零断链
- 同步 C 源码注释中的文档路径引用(D-6 重组后),构建 0 错误 0 警告
2026-09-26 00:51:53 +08:00

219 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# cppcheck C 静态分析 — 使用说明
> 本文档是 `docs/代码规范/pre-commit使用说明.md` 的配套详解,聚焦 cppcheck 钩子的检查规则、配置参数与误报抑制方法。
> pre-commit 的整体部署架构与其他钩子规则见 `docs/代码规范/pre-commit使用说明.md`。
---
## 1. 概述
cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法分析和数据流推理发现潜在缺陷。
**工作原理**:逐个读取 C 文件 → 分析语法/数据流/控制流 → 发现问题则报错。
**与 pre-commit 的关系**:`pre-commit` 阶段的 `cppcheck` 钩子调用 cppcheck 命令行,任一已启用类别发现问题即**阻止提交**(`--error-exitcode=1` 对 warning/performance/portability/style 全部生效)。
### 日常速度
- **增量扫描**(`git commit` 时):只检查 staged 文件,单文件约 1~2 秒
- **全库扫描**(`pre-commit run --all-files`):约 5 秒,用于配置变更后验证
---
## 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)生效:任何一级发现问题都会**阻止提交**。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` 与路径必须分成两个元素:
```yaml
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` 掉整条规则时,在**报错行的上一行**加注释:
```c
// 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 节。
---