- 开发工具
- 静态分析
- 代码质量
- 质量保障
【免费下载链接】cppcheck
static analysis of C/C++ code
本篇技术指南围绕 Cppcheck 静态分析器的nullPointerArithmeticOutOfMemory检查器展开,说明它如何在 C/C++ 代码中识别"内存分配函数可能返回 NULL、却未经检查就对结果指针做算术运算"的未定义行为。读完本文,你将掌握该检查器的触发条件、消息格式、底层 ValueFlow 实现原理,以及它在 lib/checknullpointer.cpp 与 test/testnullpointer.cpp 中的真实工作方式,并学会编写与之配套的修复方案与回归测试。
检查器速览
| 属性 | 值 |
|---|---|
| 检查器 ID | nullPointerArithmeticOutOfMemory |
| 默认消息 | If memory allocation fails: pointer addition with NULL pointer. |
| 类别(Category) | Undefined Behaviour(未定义行为) |
| 严重度(Severity) | Error |
| 适用语言 | C/C++ |
| 相关 CWE | CWE-682(计算错误,见源码中CWE_INCORRECT_CALCULATION) |
该检查器属于 Cppcheck 空指针检查族(CheckNullPointer),源码位于 lib/checknullpointer.cpp,相关文档即本文件 man/checkers/nullPointerArithmeticOutOfMemory.md。
问题本质:分配失败与指针算术为什么不能共存
原文档的描述非常简洁:对来自"可能失败并返回 NULL"的内存分配函数(如malloc,以及某些配置下的new)返回的指针,在未先检查分配是否成功的情况下直接执行指针算术运算。
关键点在于"算术"与"解引用"的区别:*(p+2) = 0这类代码中,即使第一步p+2并不解引用任何内存,只要p实际为 NULL,p+2本身就是未定义行为。因此,Cppcheck 不会等到真正解引用(那属于nullPointerOutOfMemory检查器)才报告——在内存压力下,任何作用于分配结果的下一个操作都可能踩中 UB。
文档的 Motivation 段落将其与 nullPointerOutOfMemory.md 直接对齐:假设分配永远成功,意味着把"可能为 NULL 的指针"继续用于算术甚至比较、索引,都是错误的。若分配结果被用于解引用,则报告nullPointerOutOfMemory;若仅用于算术(+、-、+=、-=、++、--),则报告本检查器。
触发条件与检测逻辑:从源码看实现
该检查器不是独立的检测函数,而是CheckNullPointerImpl::arithmetic()在识别到空指针算术后、根据 ValueFlow 中"该空指针可能来自分配失败"这一属性动态升级 ID 的结果。核心流程如下。
第一步:扫描所有函数体内的算术运算符
在 lib/checknullpointer.cpp#L480-L516 的arithmetic()中,检查器遍历符号数据库中的每个函数作用域,用Token::Match(tok, "-|+|+=|-=|++|--")定位所有算术运算节点,随后:
- 通过
astOperand1()/astOperand2()判断哪个操作数是指针类型(valueType()->pointer != 0); - 确认另一个操作数是整数(
isIntegral()),排除指针与浮点等非法组合; - 若数值操作数在 ValueFlow 中确定为
0,则跳过——与 0 的算术不告警; - 从指针操作数上取 ValueFlow 值(
pointerOperand->getValue(0)),若其可能为 NULL 且存在条件分支,则发出nullPointerArithmeticRedundantCheck警告(见下文);否则进入错误上报。
第二步:根据空指针来源动态生成 ID
在 pointerArithmeticError(),lib/checknullpointer.cpp#L527-L555 中,错误消息与 ID 由 ValueFlow 值的unknownFunctionReturn属性决定:
- 普通场景:ID 为
nullPointerArithmetic,消息形如Pointer addition with NULL pointer.;减法场景为Overflow in pointer arithmetic, NULL pointer is subtracted.; - 空指针来自内存分配失败(
UnknownFunctionReturn::outOfMemory):消息前缀改为If memory allocation fails: ...(首字母小写),ID 追加为nullPointerArithmeticOutOfMemory; - 空指针来自资源分配失败(
UnknownFunctionReturn::outOfResources):前缀为If resource allocation fails: ...,ID 追加为nullPointerArithmeticOutOfResources。
这也解释了为什么检查器 ID 会携带OutOfMemory后缀——它是同一检测路径在 ValueFlow 证据加持下的细化分类,便于用户用--suppress=nullPointerArithmeticOutOfMemory单独压制或按 ID 统计。
该错误固定以Severity::error上报并关联CWE_INCORRECT_CALCULATION(CWE-682),Certainty视 ValueFlow 证据是否确定为inconclusive。若结论是不确定的(inconclusive),需要用户在命令行启用--inconclusive才会报告,参见arithmetic()中对mSettings.certainty.isEnabled(Certainty::inconclusive)的判断。
第三步:与条件分支重叠时降级为警告
当 ValueFlow 显示"指针可能为 NULL 但已经存在相关条件检查"时,arithmetic()会改走redundantConditionWarning()(lib/checknullpointer.cpp#L557-L574),以Severity::warning上报 ID 为nullPointerArithmeticRedundantCheck的消息(如Either the condition '!s' is redundant or there is pointer arithmetic with NULL pointer.),提示条件冗余或算术仍可能越界,属于同族检查器的"冗余条件"变体。
底层原理:ValueFlow 如何标记"分配可能失败"
nullPointerArithmeticOutOfMemory之所以能识别出空指针来自malloc等分配函数,依赖 ValueFlow 对未知函数返回的建模,实现在 lib/valueflow.cpp#L7225-L7249:
- 首先通过
settings.library.getAllocFuncInfo()判断当前调用是否登记为库配置中的分配函数; - 若该函数带
noFail标志(即"不可能失败"),则不注入NULL 可能值; - 若该函数返回指针且无
noFail,则注入一个possible的值0,并在错误路径(errorPath)中记录Assuming allocation function fails; - 关键分流:若分配函数属于
Library::ismemory(groupId)(即库配置将其归为内存分配,如malloc、realloc、new),则标记unknownFunctionReturn = outOfMemory;否则标记为outOfResources。
也就是说,"哪些函数会被当作 failable 分配函数"由 Cppcheck 的库配置文件决定(例如 cfg/std.cfg、cfg/posix.cfg 中的函数定义及其noreturn、alloc、noFail等属性)。这解释了原文档中"newin some configurations"的措辞:new是否被视为可失败的分配函数取决于使用的平台配置与库配置。pointerArithmeticError()正是读取这个unknownFunctionReturn属性来区分...OutOfMemory与...OutOfResources的。
真实测试用例:一行代码复现与验证
仓库自带针对该检查器的回归测试,位于 test/testnullpointer.cpp#L4399-L4419 的nullpointerOutOfMemory()测试中:
void f() { int *p = malloc(10); *(p+2) = 0; free(p); }期望输出:
[test.cpp:3:8]: (error) If memory allocation fails: pointer addition with NULL pointer. [nullPointerArithmeticOutOfMemory]注意测试断言的是第 3 行*(p+2)中的加法表达式——malloc的返回值被 ValueFlow 标记为"可能为 NULL",随后直接参与+2算术,于是以nullPointerArithmeticOutOfMemory上报;而同一测试中仅做解引用的*p = 0则对应报告nullPointerOutOfMemory(warning 级别)。两行代码的对照恰好印证了原文档中"算术版 / 解引用版"两个检查器的分工。
手工验证同样简单:将上述代码保存为test.c后执行cppcheck test.c即可复现(如需报告不确定证据,追加--inconclusive)。检查器属于Severity::error,默认即启用,无需额外--enable;其 ID 同时登记在 lib/settings.cpp(第 389、453、515 行附近)的检查器清单中,参与命令行层面的开关与压制管理。
相邻检查器与组合使用
原文档的 Related checkers 部分定义了该检查器的"亲属关系",结合源码可以更精确地理解:
- nullPointerArithmetic.md:同样的算术检测逻辑,但空指针来源不特定于可失败的分配函数(例如可能是未初始化、显式赋 NULL 等),ID 不带
OutOfMemory后缀,消息为Pointer addition with NULL pointer.; - nullPointerOutOfMemory.md:同一 ValueFlow 证据(分配失败)下的直接解引用版本,消息为
If memory allocation fails, then there is a possible null pointer dereference: p,通常以 warning 上报; nullPointerArithmeticRedundantCheck:存在相关条件检查时的警告变体,用于提示条件冗余或算术越界。
此外,在 lib/checkersidmapping.cpp 中,该检查族被映射到 MISRA C 规则a5-3-2(CERTEXP34)等合规标准,意味着在启用 MISRA/CERT 配置的项目中,nullPointerArithmetic系列会作为对应规则的落地实现被触发,适合需要做安全合规审计的团队。
修复建议
修复模式非常直接:在使用分配结果之前检查其是否为空。
void f() { int *p = malloc(10); if (p == NULL) { // 处理分配失败:记录错误、返回或走备选路径 return; } *(p + 2) = 0; free(p); }加入检查后,ValueFlow 会在p+2处推导出p非空,不再上报nullPointerArithmeticOutOfMemory。若代码必须让分配结果"裸奔"参与算术,则应改用不会失败或带异常语义的分配方式(如std::vector、new的nothrow与显式检查组合等),确保"分配失败后的任何指针操作"都不进入未定义行为区间。
小结
nullPointerArithmeticOutOfMemory是 Cppcheck 空指针检查族中专门针对"可失败内存分配 + 未经检查的指针算术"的 Error 级检查器。它并非独立算法,而是CheckNullPointer::arithmetic()借助 ValueFlow 的unknownFunctionReturn::outOfMemory证据对通用空指针算术错误做的细化分类。理解它的关键链条是:库配置(cfg 系列)声明分配函数 → ValueFlow 注入"可能为 NULL"(lib/valueflow.cpp)→arithmetic()扫描算术运算符 →pointerArithmeticError()依据证据拼接消息与 ID。配合 test/testnullpointer.cpp 中的回归用例,你可以在自己的 CI 流程中轻松复现、压制或扩展这一检查。
- 开发工具
- 静态分析
- 代码质量
- 质量保障
【免费下载链接】cppcheck
static analysis of C/C++ code
相关推荐
Cppcheck 检查器详解:arithOperationsOnVoidPointer 与 void* 指针算术的可移植性隐患
Cppcheck 检查器详解:arithOperationsOnVoidPointer 与 void 指针算术的可移植性隐患 导读 arithOperation
开发工具静态分析代码质量质量保障cppcheck 跨编译单元(Whole-Program)空指针解引用检测:ctunullpointer 检查器完全指南
cppcheck 跨编译单元(Whole Program)空指针解引用检测:ctunullpointer 检查器完全指南 本文围绕 cppcheck 的 ctu
开发工具静态分析代码质量质量保障cppcheck 的 deallocret 检查器:捕获“释放后返回指针”的未定义行为
cppcheck 的 deallocret 检查器:捕获“释放后返回指针”的未定义行为 导读 deallocret 是 cppcheck 内置的 C/C++ 静
开发工具静态分析代码质量质量保障
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考