4. Cppcheck 实战:安装、配置、命令行使用、自定义规则编写
好,咱们进入实战环节。前面讲了那么多理论,说白了,工具得用起来才有价值。这一章我带你亲手把 Cppcheck 玩转,从安装到写自定义规则,一步不落。最后我会分享一个真实案例——我在一个电机控制项目里,用它抓到了一个隐藏极深的空指针。
4.1 安装与基础配置
Cppcheck 的安装,其实没什么难度。我习惯在 Linux 下做开发,一条命令搞定:
sudo apt-get install cppcheck
Windows 用户去官网下载安装包就行。macOS 用户用 brew:
brew install cppcheck
装完之后,验证一下:
cppcheck --version
嗯,看到版本号就说明成了。
我个人习惯在项目根目录放一个 .cppcheck 配置文件。这样团队里每个人跑出来的结果都一样,避免「你机器上能过,我机器上就报错」的尴尬。
# .cppcheck 示例
--suppress=missingIncludeSystem
--enable=all
--inconclusive
--std=c99
--platform=unix64
--output-file=cppcheck_report.xml
--xml
4.2 命令行使用技巧
命令行才是 Cppcheck 的精髓。你想想看,CI 流程里不可能有人盯着 GUI 点点点,对吧?
最基本的用法:
cppcheck --enable=all --suppress=missingIncludeSystem ./src
这行命令会检查 ./src 目录下所有 C/C++ 文件,启用所有检查项,同时忽略头文件找不到的警告(嵌入式项目里经常有交叉编译的头文件路径问题)。
我常用的几个参数:
| 参数 | 作用 | 我的经验 |
|---|---|---|
| --enable=all | 启用所有检查 | 别怕误报,宁可错杀一千 |
| --suppress= | 抑制特定警告 | 第三方库的警告直接压掉 |
| --xml | 输出 XML 格式 | CI 解析用,方便集成 |
| --std=c99 | 指定 C 标准 | 嵌入式老项目常用 C99 |
| -j 4 | 多线程检查 | 大项目能快 3-4 倍 |
举个例子,我在一个电机控制项目里,代码量大概 10 万行。不加 -j 要跑 40 秒,加上 -j 4 只要 12 秒。CI 流水线里,每一秒都很宝贵。
4.3 自定义规则编写
Cppcheck 最强大的地方,就是你可以写自己的规则。说白了,就是写一个 C++ 插件,让 Cppcheck 在分析代码时调用你的逻辑。
为什么要写自定义规则?我遇到过这种情况:项目里有个约定,所有 malloc 之后必须检查返回值。但 Cppcheck 默认不检查这个。怎么办?自己写一个规则呗。
创建一个 myrule.cpp:
#include "cppcheck.h"
#include "tokenize.h"
class MyRule : public Check {
public:
MyRule() : Check(myName()) {}
static std::string myName() {
return "MyRule";
}
void runChecks(const Tokenizer &tokenizer, ErrorLogger &errorLogger) override {
// 遍历所有 token
for (const Token *tok = tokenizer.tokens(); tok; tok = tok->next()) {
// 检查 malloc 后是否立即检查 NULL
if (tok->str() == "malloc") {
const Token *next = tok->next();
if (next && next->str() == ";") {
// malloc 后面直接跟分号,没有检查 NULL
errorLogger.reportError(
tok,
Severity::warning,
"mallocWithoutCheck",
"malloc 后未检查返回值是否为 NULL",
"CWE-476"
);
}
}
}
}
};
// 注册插件
extern "C" Check *createCheck() {
return new MyRule();
}
编译成动态库:
g++ -shared -fPIC -o myrule.so myrule.cpp -I/path/to/cppcheck/include
使用自定义规则:
cppcheck --library=myrule.so ./src
4.4 实战案例:电机控制项目中的空指针
好,重点来了。我在一个电机控制项目里,用 Cppcheck 抓到了一个空指针。这个 bug 藏得很深,代码审查都没发现。
项目背景:一个无刷直流电机(BLDC)的 FOC 控制算法。代码是前辈写的,跑了两年都没出问题。直到有一次,电机在特定工况下突然停转。
我接手后,第一件事就是跑 Cppcheck:
cppcheck --enable=all --suppress=missingIncludeSystem --xml ./src 2> report.xml
结果出来,有一条警告让我眼前一亮:
./src/motor_control.c:247: error: Possible null pointer dereference: pwm_config
我打开代码一看:
void motor_set_pwm(MotorConfig *config) {
PWMConfig *pwm_config = config->pwm; // 这里可能为 NULL
pwm_config->frequency = 20000; // 直接解引用
pwm_config->duty_cycle = 0.5;
}
问题出在哪?config->pwm 在初始化时可能为 NULL。正常情况下,初始化代码会先分配 pwm_config,但有一个异常路径跳过了分配。这个路径只在电机启动电流过大时触发,概率极低。
修复很简单:
void motor_set_pwm(MotorConfig *config) {
PWMConfig *pwm_config = config->pwm;
if (pwm_config == NULL) {
// 记录错误日志,返回安全值
log_error("PWM config is NULL");
return;
}
pwm_config->frequency = 20000;
pwm_config->duty_cycle = 0.5;
}
我曾经以为这种低级错误不会出现在成熟项目里。但现实是,越复杂的系统,越容易有这种「灯下黑」的问题。从那以后,我把 Cppcheck 加到了 CI 的必过环节,任何空指针警告都不允许合并。
4.5 集成到 CI 的最佳实践
最后,说说怎么把 Cppcheck 集成到 CI 里。我用的 Jenkins,但思路通用。
- 设置阈值:我一般设 error 为 0,warning 不超过 10 个。超过就构建失败。
- 生成报告:用 --xml 输出,然后用 Jenkins 的 Warnings Next Generation 插件解析。
- 增量检查:大项目全量检查太慢。我写了个脚本,只检查 git diff 涉及的文件。
- 基线管理:第一次跑出来的警告,建立基线。后续只报告新增的警告。
嗯,这一章内容不少。从安装到自定义规则,再到实战案例,你应该对 Cppcheck 有了全面的认识。下一章,咱们聊聊如何把 Cppcheck 和 Jenkins 深度整合,实现自动化门禁。