- 开发工具
- 静态分析
- 代码质量
- 质量保障
【免费下载链接】cppcheck
static analysis of C/C++ code
导读
本文以 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++ |
| 检查器 ID | overlappingWriteFunction |
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)解析为"基指针 + 字节偏移",它支持三种表达式形态:
&a[i]形式:提取数组基地址与下标,按下标元素大小换算字节偏移;- 指针算术
a + i/i + a形式:根据 valueType 判断哪一侧是指针,计算元素大小与偏移; - 纯指针表达式(无偏移):偏移记为 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 合规检查。
实战建议
- 优先改用
memmove():凡源/目标可能来自同一缓冲区(数组移位、就地平移、重叠区拷贝),直接用memmove()消除 UB,无需分析具体偏移。 - 注意"恰好相邻"是安全的:
memcpy(&a[0], &a[4], 4u)这类拷贝不会触发检查,因为区间不重叠;但边界条件一旦随代码演进(如大小变为2 * sizeof)立即会被报告。 - 大小必须可静态确定:如果拷贝大小来自运行时变量,本检查器不会告警(遵循"精确性优先"),此时应人工审视或借助其他手段确认区间关系。
- 库函数可扩展:
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
相关推荐
cppcheck overlappingWriteUnion 检查器详解:捕获联合体同一表达式中的重叠读写未定义行为
cppcheck overlappingWriteUnion 检查器详解:捕获联合体同一表达式中的重叠读写未定义行为 本指南围绕 cppcheck 静态分析器的
开发工具静态分析代码质量质量保障cppcheck floatConversionOverflow 检查器详解:浮点转整数的未定义行为检测
cppcheck floatConversionOverflow 检查器详解:浮点转整数的未定义行为检测 导读 本文深入讲解 cppcheck 的 floatC
开发工具静态分析代码质量质量保障Cppcheck autoVariables 检查器深度解析:局部变量地址经函数参数泄漏的未定义行为检测
Cppcheck autoVariables 检查器深度解析:局部变量地址经函数参数泄漏的未定义行为检测 本文围绕 Cppcheck 的 autoVariabl
开发工具静态分析代码质量质量保障
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考