ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

cppcheck 的 operatorEqToSelf 检查详解:如何发现并修复 C++ 拷贝赋值运算符的自赋值缺陷

2026/10/5 11:36:40 拓冰建站 浏览量
cppcheck 的 operatorEqToSelf 检查详解:如何发现并修复 C++ 拷贝赋值运算符的自赋值缺陷 开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载导读operatorEqToSelf是 cppcheck 提供的 Correctness正确性类检查专门用于检测 C 类中那些会先释放自身资源、再从源对象拷贝数据的拷贝赋值运算符operator——这种写法在遭遇a a;这类自赋值时会先释放掉即将读取的数据造成对象损坏对已释放内存的读取属于未定义行为。本文将以 cppcheck 仓库中 man/checkers/operatorEqToSelf.md 为骨架结合 lib/checkclass.cpp 中的检查实现与 test/testclass.cpp 中的测试用例完整说明该检查的消息格式、触发原理、检测逻辑边界以及修复方案帮助你既能在代码评审中看懂这条告警也能理解 cppcheck 内部是如何做出判断的。检查速览消息、类别与严重级别该检查在文档中定义的基础信息如下项目值Message消息文本operator should check for assignment to self to avoid problems with dynamic memory.Category类别CorrectnessSeverity严重级别WarningLanguage适用语言C在源码层面这条消息由 lib/checkclass.cpp#L1992-L1998 中的CheckClassImpl::operatorEqToSelfError()发出void CheckClassImpl::operatorEqToSelfError(const Token *tok) { reportError(tok, Severity::warning, operatorEqToSelf, operator should check for assignment to self to avoid problems with dynamic memory.\n operator should check for assignment to self to ensure that each block of dynamically allocated memory is owned and managed by only one instance of the class., CWE398, Certainty::normal); }可以看到完整告警除了文档中给出的一行主消息外还附带一句补充说明每个动态分配内存块应只被类的一个实例所拥有和管理并关联了 CWE-398代码质量缺陷以及 normal 级别的确定性Certainty。该检查的严重级别warning与 lib/checkers.cpp#L67 中的注册信息{CheckClass::operatorEqToSelf,warning}一致同时它还映射到 CERT 编码规则 OOP54 与 MISRA C 2023 的Dir 15.8.1见 lib/checkersidmapping.cpp#L119 与 lib/checkersidmapping.cpp#L215。问题本质为什么a a;会破坏对象a a;是合法但反常的代码。如果operator在把源对象的内容拷贝进来之前先释放掉自己当前持有的资源那么在自赋值时它释放的恰恰是它正要读取的那份数据——释放后随即从已释放的内存读取use-after-free属于未定义行为对象随之损坏。需要特别强调的是cppcheck 的这条检查并不要求它能够证实新值确实来自已释放的数据。正如原文档所指出的cppcheck 会标记operator中任何分配资源却不做自检查的模式而不仅仅是它能够确认会读取已释放内存的那种情况。也就是说只要operator中存在释放资源 重新分配 拷贝的典型组合并且缺少自赋值保护就足以触发告警。这是一种保守但合理的策略自赋值在普通代码中很少显式出现通常是通过别名、引用或容器操作间接发生因此这类缺陷可能长期潜伏而无人察觉——这也正是该检查的实用价值所在。一个真实损坏场景以原文档的示例为例下面的A::operator先free(s)再strdup(a.s)#include cstring #include cstdlib class A { public: char *s; A operator(const A a) { free(s); // - breaks if a is *this s strdup(a.s); return *this; } };当发生自赋值例如通过A ref obj; obj ref;或容器重排元素间接触发时free(s)先释放了*this的缓冲区若a正是*this那么a.s指向的正是刚刚释放的同一块内存strdup(a.s)试图从已释放的内存读取字符串行为未定义即便没有立即崩溃对象也可能留下悬空指针或损坏状态。修复方法拷贝前先做自赋值检查标准修复方式是在释放任何资源之前先判断this a若成立则直接返回*this#include cstring #include cstdlib class A { public: char *s; A operator(const A a) { if (this a) return *this; free(s); s strdup(a.s); return *this; } };等效写法同样被 cppcheck 认可例如if (a ! this)包裹整个释放与拷贝逻辑或在条件成立时return *this;提前退出。测试用例 test/testclass.cpp#L1752-L1768 中展示了这一被认可的模式class A { public: char *s; A operator(const A a) { if (a ! this) { free(s); s strdup(a.s); } return *this; } };cppcheck 是如何检测的核心实现剖析检测入口位于 lib/checkclass.cpp#L1811-L1850 的CheckClassImpl::operatorEqToSelf()。它逐条枚举符号数据库中的类/结构体作用域并按以下条件筛选目标函数。第一层筛选什么才算可检查的 operator函数必须是带函数体的operatorFunctionType::eOperatorEqual func.hasBody()跳过多重继承的类。因为一个对象在多重继承下可能拥有多个地址this a这种基于单一地址的判断不再可靠因此没有平凡的办法检测自赋值代码注释原文见 lib/checkclass.cpp#L1806-L1808参数必须是本类类型。实现会回退到参数类型的结束 token剥离const、、*修饰符后与类名比较lib/checkclass.cpp#L1825-L1832保证检测的是同类型对象间的赋值而非operator(int)之类的重载返回签名必须是类名 lib/checkclass.cpp#L1835。第二层判断有没有自检查有没有动态内存操作通过上述筛选后实现调用两个辅助函数做核心判断hasAssignSelf()lib/checkclass.cpp#L1958-L1990在函数体内查找if (...)条件中形如this param或this ! param的比较hasAssignSelf返回时同时通过出参带回对应 if 语句体的起始位置供后续判断hasAllocation()lib/checkclass.cpp#L1862-L1897在指定 token 范围内扫描三类动态内存操作x new ...Cnew分配x func(...)且func在.cfg库配置中登记为分配函数mSettings.library.getAllocFuncInfo释放操作delete x、delete [] x或库配置中登记的释放函数getDeallocFuncInfo——并且释放后该成员变量又被重新赋值才视为动态内存模式成立。最终判定逻辑为若没有自赋值检查但存在分配/释放模式 → 报错若有自赋值检查但检查位于if条件为反向的分支例如if (a this) { free(...); s strdup(...); }即只有自赋值时才释放拷贝→ 也报错hasAllocationInIfScope见 lib/checkclass.cpp#L1852-L1860。这里反向测试的判定由isInverted()lib/checkclass.cpp#L1913-L1940完成它会沿 AST 逐层识别!、、!以及与true/false的组合来对布尔结果取反从而判断 if 分支到底保护的是自赋值还是非自赋值。从源码结构可推断的检测边界综合上述实现可以归纳出该检查的边界行为误报受控类没有动态内存操作、operator无函数体、参数不是本类类型、返回类型不是引用或类使用多重继承时均不会触发保守全面只要存在释放 重新分配/拷贝模式而缺少正确方向的自检查就会告警无论能否静态证实该自赋值必然读到已释放内存支持各种等价的写法变体this a、a this、this ! a、!(a this)以及带true/false的复合布尔表达式均能被识别对应测试见 test/testclass.cpp#L2257-L2537。测试用例边界与回归保障该检查在 test/testclass.cpp 中拥有 9 组命名测试operatorEqToSelf1至operatorEqToSelf9注册于 test/testclass.cpp#L78-L86覆盖了非常丰富的场景测试组覆盖内容operatorEqToSelf1单类有/无自检查、有/无动态内存、内联与外联类内声明 类外定义四种组合以及大量反向/变形布尔条件operatorEqToSelf2嵌套类中的同名检测operatorEqToSelf3/operatorEqToSelf4多重继承普通类与嵌套类——即使有分配也不报错operatorEqToSelf5多种等价自检查写法ticket #1233包括if((a!this))、if(!(thisa))、if(false(athis))等operatorEqToSelf6delete []/deletenew 拷贝模式ticket #1550含类内与类外定义两种形式operatorEqToSelf7operator委托给assign()成员函数时不告警operatorEqToSelf8operator委托给copy()成员函数时不告警operatorEqToSelf9存在operator(Foo*)与operator(Foo)两个重载、互相委托时不告警值得注意的是最后三组当operator把实际工作委托给另一个成员函数如return assign(a);时cppcheck 不会继续穿透分析被调用函数的内部实现因此不会告警见 test/testclass.cpp#L2621-L2674。这一行为也从侧面说明如果项目里operator采用委托给公共 copy 函数的模式需要在被委托函数中自行保证自赋值安全因为该检查不会追踪到这里。测试还覆盖了参数类型不匹配的情况operator(const B b)中s strdup(a.s)引用了不存在的a见 test/testclass.cpp#L2539-L2549此时因参数不是本类类型而被正确跳过。如何在项目中启用与使用命令行方式operatorEqToSelf属于warning严重级别注册表见 lib/checkers.cpp#L67因此在默认配置下即已启用。命令行可直接运行cppcheck --enablewarning your_file.cpp若要仅输出该检查项可配合 XML 输出后按 id 过滤或用--suppressoperatorEqToSelf排除同时支持行内// cppcheck-suppress operatorEqToSelf抑制注释。更精细地可以结合仓库自带的--checkers-report或--checkers查看当前配置下该检查的启用状态cppcheck --checkers --enablewarning your_file.cppGUI 方式在 cppcheck GUIgui/ 目录中该检查位于 C 类的 Correctness 类告警下可在设置中按类别过滤 Warning 级别的正确性告警从而高亮operatorEqToSelf报告。与其他检查的联动selfAssignmentlib/checkother.cpp 中的CheckOtherImpl::selfAssignmentError关注的是代码中直接出现的a a赋值表达式本身而operatorEqToSelf关注的是operator缺少防御自赋值的逻辑两者互补operatorEqRetRefThis同样位于 lib/checkclass.cpp检查operator是否正确返回*this与该检查共同构成对拷贝赋值运算符完整性的保障。小结operatorEqToSelf是 cppcheck 针对 C 资源管理类拷贝赋值运算符的重要正确性检查。它通过存在动态内存释放/分配模式 缺少或反向的自赋值保护这一保守规则捕获那些可能在a a;或经别名、容器操作间接触发的自赋值时产生 use-after-free 的潜在缺陷。理解其检测逻辑——包括多重继承豁免、参数与返回类型筛选、this a各种等价写法识别——能帮助你在消除告警的同时写出更健壮的拷贝赋值运算符。修复的核心始终是一句提前返回if (this a) return *this;。延伸阅读仓库内检查文档man/checkers/operatorEqToSelf.md核心实现lib/checkclass.cpp#L1811-L1998含operatorEqToSelf、hasAssignSelf、hasAllocation、isInverted等辅助函数检查注册lib/checkers.cpp#L67规则映射lib/checkersidmapping.cpp#L119CERT OOP54 / MISRA CDir 15.8.1测试用例test/testclass.cpp#L78-L86 与 test/testclass.cpp#L1733-L2674赞分享开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载相关推荐Go 赋值运算实战复合赋值、自增自减与 strconv.ParseFloat 的常见陷阱Go 赋值运算实战复合赋值、自增自减与 strconv.ParseFloat 的常见陷阱 导读 本文围绕 learngo 仓库中 08 numbers and示例工程教程Cppcheck assignBoolToPointer 检查深度解析布尔值赋值给指针的隐患与修复Cppcheck assignBoolToPointer 检查深度解析布尔值赋值给指针的隐患与修复 导读 assignBoolToPointer 是 Cppc开发工具静态分析代码质量质量保障cppcheck duplicateConditionalAssign 检查详解识别与 if 条件重复的冗余赋值cppcheck duplicateConditionalAssign 检查详解识别与 if 条件重复的冗余赋值 duplicateConditionalAs开发工具静态分析代码质量质量保障上一篇终极指南如何为 Anti-Adblock Killer 贡献代码和过滤器 下一篇LittleFS定制开发如何根据特定需求扩展文件系统功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考