☰
Cppcheck 空指针检查器详解:nullPointerArithmeticOutOfMemory(内存分配失败后的指针算术)
2026/10/5 6:36:27 网站建设 项目流程
  • 开发工具
  • 静态分析
  • 代码质量
  • 质量保障

【免费下载链接】cppcheck

static analysis of C/C++ code

项目地址:https://gitcode.com/gh_mirrors/cpp/cppcheck
点击查看免费下载

本篇技术指南围绕 Cppcheck 静态分析器的nullPointerArithmeticOutOfMemory检查器展开,说明它如何在 C/C++ 代码中识别"内存分配函数可能返回 NULL、却未经检查就对结果指针做算术运算"的未定义行为。读完本文,你将掌握该检查器的触发条件、消息格式、底层 ValueFlow 实现原理,以及它在 lib/checknullpointer.cpp 与 test/testnullpointer.cpp 中的真实工作方式,并学会编写与之配套的修复方案与回归测试。

检查器速览

属性值
检查器 IDnullPointerArithmeticOutOfMemory
默认消息If memory allocation fails: pointer addition with NULL pointer.
类别(Category)Undefined Behaviour(未定义行为)
严重度(Severity)Error
适用语言C/C++
相关 CWECWE-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, "-|+|+=|-=|++|--")定位所有算术运算节点,随后:

  1. 通过astOperand1()/astOperand2()判断哪个操作数是指针类型(valueType()->pointer != 0);
  2. 确认另一个操作数是整数(isIntegral()),排除指针与浮点等非法组合;
  3. 若数值操作数在 ValueFlow 中确定为0,则跳过——与 0 的算术不告警;
  4. 从指针操作数上取 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

项目地址:https://gitcode.com/gh_mirrors/cpp/cppcheck
点击查看免费下载

相关推荐

上一篇:3步打造高性能多线程内存数据库:KeyDB实战指南
下一篇:Claude Ads 付费媒体规划指南:从广告策略蓝图到可执行的媒体计划

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询