☰
cppcheck overlappingWriteFunction 检查器完全指南:检测 memcpy() 等函数的重叠读写未定义行为
2026/10/5 6:36:59 网站建设 项目流程
  • 开发工具
  • 静态分析
  • 代码质量
  • 质量保障

【免费下载链接】cppcheck

static analysis of C/C++ code

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

导读

本文以 cppcheck 官方检查器文档 man/checkers/overlappingWriteFunction.md 为骨架,结合 lib/checkother.cpp、cfg/std.cfg 与 test/testother.cpp 中的源码实现和测试用例,系统讲解overlappingWriteFunction检查器:它识别对memcpy()等仅允许不重叠数据区操作的函数传入重叠源/目标区间这一未定义行为(UB)。读完本文,你将掌握该检查器的触发条件、底层区间重叠判定算法、库配置(library cfg)如何声明"不允许重叠"的函数集合,以及如何用memmove()修复此类缺陷。


检查器概览

属性值
消息(Message)Overlapping read/write in memcpy() is undefined behavior
分类(Category)Undefined Behaviour(未定义行为)
严重级别(Severity)Error
适用语言(Language)C/C++
检查器 IDoverlappingWriteFunction

overlappingWriteFunction是 cppcheck 默认启用的 error 级检查器之一,由CheckOther检查类负责实现。它对应的缺陷模式是:调用memcpy()这类函数时,源区间与目标区间发生重叠——而只有memmove()家族函数被标准允许处理重叠区间,memcpy()等函数在区间重叠时属于未定义行为。

缺陷的本质:为什么 memcpy 不允许重叠

memcpy()(区别于memmove())的实现可以自由选择对平台最快的拷贝顺序——正向、反向或分块拷贝——其前提假设是源与目标从不重叠。一旦二者重叠,拷贝过程可能覆盖掉尚未读取的源数据,导致结果被破坏,而且这种破坏在不同编译器、不同优化级别、不同标准库实现之间表现各异。

因此文档给出的核心指导是:

只有当 cppcheck 能确定被拷贝的精确大小时,它才报告此问题;它不会仅仅因为两个区间"看起来可疑地接近"但大小无法确定就发出警告。

这条"精确性优先"的设计原则在源码中有直接体现,下文会展开说明。

触发条件与官方示例

官方"Before"示例(触发检查)

#include <cstring> void foo() { char a[10]; memcpy(a, a+1, 2u); // <- source and destination overlap }

这里a+1与a指向同一数组a[10]内的相邻字节,拷贝 2 字节时源区间[1, 3)与目标区间[0, 2)重叠,属于未定义行为。

官方"After"修复示例

#include <cstring> void foo() { char a[10]; memmove(a, a+1, 2u); // memmove() is defined to handle overlap safely }

memmove()被标准明确要求以"如同先拷贝到临时缓冲区再拷贝回来"的方式处理重叠,因此是安全的。

源码实现原理:checkOverlappingWrite 的工作流

该检查器由 lib/checkother.cpp 中CheckOtherImpl::checkOverlappingWrite()(见 lib/checkother.cpp#L4762-L4858)实现,在 CheckOther::runChecks 中被调度执行。整个流程可拆解为三个阶段:

阶段一:识别"不允许重叠数据"的函数

检查器遍历每个函数作用域(functionScopes)内的全部 token,当遇到形如%name% (的函数调用时,通过mSettings.library.getNonOverlappingData(tok)(lib/library.cpp#L1630-L1635)查询库配置,判断该函数是否声明了not-overlapping-data属性:

const Library::NonOverlappingData *nonOverlappingData = mSettings.library.getNonOverlappingData(tok); if (!nonOverlappingData) continue;

NonOverlappingData结构体(lib/library.h#L151-L157)记录五个参数位置:

struct NonOverlappingData { int ptr1Arg; // 第一个指针(目标)所在参数位置 int ptr2Arg; // 第二个指针(源)所在参数位置 int sizeArg; // 大小参数位置(-1 表示无大小参数) int strlenArg; // strlen 类参数位置(当前实现中有 TODO,未使用) int countArg; // 元素个数参数位置 };

这些属性来自库配置文件中的<not-overlapping-data>元素,解析代码见 lib/library.cpp#L886-L892:

nonOverlappingData.ptr1Arg = functionnode->IntAttribute("ptr1-arg", -1); nonOverlappingData.ptr2Arg = functionnode->IntAttribute("ptr2-arg", -1); nonOverlappingData.sizeArg = functionnode->IntAttribute("size-arg", -1); nonOverlappingData.strlenArg = functionnode->IntAttribute("strlen-arg", -1); nonOverlappingData.countArg = functionnode->IntAttribute("count-arg", -1);

阶段二:确定被拷贝的精确大小

这是"精确性优先"原则的落点。以memcpy在 cfg/std.cfg 中的声明为例(cfg/std.cfg#L3990-L4010):

<function name="memcpy,std::memcpy"> <not-overlapping-data ptr1-arg="1" ptr2-arg="2" size-arg="3"/> ... </function>

而wmemcpy则使用count-arg声明元素个数(cfg/std.cfg#L4011-L4031):

<function name="wmemcpy,std::wmemcpy"> <not-overlapping-data ptr1-arg="1" ptr2-arg="2" count-arg="3"/> ... </function>

源码中大小参数取sizeArg与countArg的较大者:

const int sizeArg = std::max(nonOverlappingData->sizeArg, nonOverlappingData->countArg); if (sizeArg <= 0 || sizeArg > args.size()) { // 没有大小参数(sizeArg == -1)时的特例处理,见阶段三 ... } // 大小参数必须具有已知整数值,否则放弃检查 if (!args[sizeArg-1]->hasKnownIntValue()) continue; MathLib::bigint sizeValue = args[sizeArg-1]->getKnownIntValue();

关键点:若大小参数不是编译期可确定的常量,检查器直接跳过。这正是文档所述"不会仅因两个区间可疑接近就警告"的代码依据。若使用count-arg(元素个数语义),还需乘以元素大小换算成字节数。

阶段三:解析指针偏移并判定区间是否重叠

两个指针参数经由getBufAndOffset()(lib/checkother.cpp#L4704-L4760)解析为"基指针 + 字节偏移",它支持三种表达式形态:

  1. &a[i]形式:提取数组基地址与下标,按下标元素大小换算字节偏移;
  2. 指针算术a + i/i + a形式:根据 valueType 判断哪一侧是指针,计算元素大小与偏移;
  3. 纯指针表达式(无偏移):偏移记为 0,若带大小参数则同时把大小乘以元素尺寸(如2u * sizeof(a[0])这类表达式)。

得到offset1、offset2与sizeValue后,执行区间不重叠判定:

if (offset1 < offset2 && offset1 + sizeValue <= offset2) continue; // 区间 1 完全在区间 2 之前,不重叠 if (offset2 < offset1 && offset2 + sizeValue <= offset1) continue; // 区间 2 完全在区间 1 之前,不重叠 // 否则两个区间存在重叠

注意这是闭区间判定:两个区间只要首尾相接不相交(如memcpy(&a[0], &a[4], 4u),[0,4)与[4,8))就不报告——相邻但不重叠是安全的。

最后还需确认两个基指针在语义上是同一表达式(isSameExpression),否则也不报告;确认重叠后调用overlappingWriteFunction(tok, tok->str())(lib/checkother.cpp#L4865-L4868)上报:

void CheckOtherImpl::overlappingWriteFunction(const Token *tok, const std::string& funcname) { reportError(tok, Severity::error, "overlappingWriteFunction", "Overlapping read/write in " + funcname + "() is undefined behavior"); }

消息文本中的函数名取自实际调用的函数(如memcpy、wmemcpy、strcpy),因此输出具有针对性。

无大小参数函数的特例:同一表达式直接报告

对于sizeArg == -1的函数(例如strcpy(ptr, ptr)这种没有长度参数的调用),源码走特例分支(lib/checkother.cpp#L4821-L4829):只要两个指针是完全相同的表达式,就直接报告重叠——因为完全相同意味着重叠必然发生:

if (nonOverlappingData->sizeArg == -1) { if (!isSameExpression(macro, ptr1, ptr2, mSettings, pure, follow, &errorPath)) continue; overlappingWriteFunction(tok, tok->str()); }

测试用例验证:行为边界一览

test/testother.cpp 的checkOverlappingWrite()测试(test/testother.cpp#L13878-L13969)完整验证了上述判定逻辑,是理解边界条件的黄金材料:

测试代码期望结果说明
memcpy(&a[5], &a[4], 2u)报告&a[i]下标形式重叠
memcpy(a+5, a+4, 2u)报告指针算术形式重叠
memcpy(a, a+1, 2u)报告与官方文档示例一致
memcpy(&a[0], &a[4], 4u)不报告[0,4)与[4,8)首尾相接,不重叠
memcpy(&a[5], &a[4], 2u * sizeof(a[0]))报告大小含sizeof乘法(#10350)
memcpy(&K[0], &K[1], sizeof(K[0]))不报告相邻元素各拷 1 个,不重叠(#12638)
memcpy(&K[0], &K[1], 2 * sizeof(K[0]))报告拷 2 个元素必然重叠
wmemcpy(&a[5], &a[4], 2u)报告count-arg语义生效
strcpy(ptr, ptr)报告sizeArg == -1同一表达式特例

这些用例表明:检查器对"恰好相邻不重叠"的拷贝保持克制(避免误报),只有区间真正相交时才报错。

关联检查器与规则映射

overlappingWriteFunction与 overlappingWriteUnion 是同源缺陷(重叠读写)的两种表现:前者是函数调用传入重叠区间,后者是同一表达式内对 union 两个重叠成员读写(如u.i = u.f;)。二者共享相同的消息风格与 error 级别。

在规则映射层面:

  • MISRA C 规则 19.1 被映射到这两个检查器,见 lib/checkersidmapping.cpp#L144 与 addons/misra.py#L1387;
  • MISRA 0-2-1 也覆盖这两个 ID(lib/checkersidmapping.cpp#L30)。

也就是说,在--misra-*相关配置下运行 addon 时,重叠读写问题会被同时计入 MISRA 合规检查。

实战建议

  1. 优先改用memmove():凡源/目标可能来自同一缓冲区(数组移位、就地平移、重叠区拷贝),直接用memmove()消除 UB,无需分析具体偏移。
  2. 注意"恰好相邻"是安全的:memcpy(&a[0], &a[4], 4u)这类拷贝不会触发检查,因为区间不重叠;但边界条件一旦随代码演进(如大小变为2 * sizeof)立即会被报告。
  3. 大小必须可静态确定:如果拷贝大小来自运行时变量,本检查器不会告警(遵循"精确性优先"),此时应人工审视或借助其他手段确认区间关系。
  4. 库函数可扩展:not-overlapping-data是 library cfg 的通用机制(参考 cfg/std.cfg 中memcpy、wmemcpy的写法),自定义库函数若也要求不重叠,可按ptr1-arg/ptr2-arg/size-arg/count-arg语法在 cfg 中声明,使检查器自动覆盖。

参考路径

  • 官方检查器文档:man/checkers/overlappingWriteFunction.md
  • 关联检查器文档:man/checkers/overlappingWriteUnion.md
  • 检查器实现:lib/checkother.cpp、声明见 lib/checkother.h#L266
  • 库配置解析:lib/library.cpp#L886-L892、lib/library.h#L151-L158
  • 函数声明示例:cfg/std.cfg#L3990-L4031
  • 测试用例:test/testother.cpp#L13878-L13969
  • MISRA 规则映射:lib/checkersidmapping.cpp#L144、addons/misra.py#L1387
  • 开发工具
  • 静态分析
  • 代码质量
  • 质量保障

【免费下载链接】cppcheck

static analysis of C/C++ code

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

相关推荐

上一篇:OwlCarousel2 代码风格检查:JSHint配置与常见错误修复
下一篇:LunaTranslator 语音识别(Speech Recognition)功能实战指南:Windows 语音识别两种模式详解

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

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

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

立即咨询