ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

cppcheck 检查器深度解析:copyCtorPointerCopying —— 复制构造函数浅拷贝指针导致的悬垂与 double-free 风险

cppcheck 检查器深度解析:copyCtorPointerCopying —— 复制构造函数浅拷贝指针导致的悬垂与 double-free 风险 开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载本文是 cppcheck 静态分析工具 copyCtorPointerCopying 检查器的专项技术指南。它面向使用 C 手写复制构造函数的开发者讲解 cppcheck 如何识别“复制构造函数直接拷贝指针成员值浅拷贝而未为新对象分配独立内存”的经典错误并给出从问题本质、源码检测原理到修复方案的完整闭环。读完本文你将掌握该检查器的触发条件、底层实现机制以及一套可直接落地的深拷贝修复写法。检查器基本信息属性值检查器 IDcopyCtorPointerCopying消息文本Value of pointer p, which points to allocated memory, is copied in copy constructor instead of allocating new memory.类别Undefined Behaviour严重级别Warning适用语言C该检查器由 lib 层的类检查模块实现最终错误上报入口位于 lib/checkclass.cpp 的copyConstructorShallowCopyError()上报时携带 CWE 编号CWE-398Indicator of Poor Code Quality以及Certainty::normal的确定性级别并在 lib/checkclass.h 中声明。问题本质拷贝的是指针值而不是指针指向的数据复制构造函数copy constructor的本职是为新对象建立一份与源对象等价但相互独立的副本。当类成员是指向已分配内存的裸指针时正确做法是为新对象分配一块全新内存再把源对象指针指向的数据内容拷贝进去——即深拷贝。而该检查器捕获的错误模式是复制构造函数直接执行p f.p之类的赋值把指针成员本身的值即内存地址复制给了新对象。结果是两个对象共享同一块已分配内存各自却都认为这块内存归自己所有——这就是经典的浅拷贝 bug通常紧跟着的便是两个对象销毁时对同一块内存的重复释放double-free。cppcheck 对该错误的触发有一个明确的限定被拷贝的指针必须指向已分配内存即类中存在new/malloc家族的分配动作而不是任意指针成员的随意拷贝。触发条件小结从检测实现看copyCtorPointerCopying的触发需要同时满足以下条件类或结构体中存在构造函数且构造函数的初始化列表或函数体中出现了new/malloc等分配动作%var% ( new或%var% ( %name% (且该函数被 library 数据标记为分配函数类中定义了复制构造函数FunctionType::eCopyConstructor且该复制构造函数不是 default的复制构造函数体内存在p f.p;形式的指针成员逐值拷贝其中p正是上述已被记录为“已分配”的成员复制构造函数体内没有重新为该成员分配内存p new ...或调用分配函数会被视为已重新分配并解除告警。对应实现见 lib/checkclass.cpp 的CheckClassImpl::copyconstructors()。为什么危险double-free 与悬垂指针的连锁反应一旦两个对象持有相同的指针值、且各自都认为自己是这块内存的所有者会发生如下连锁反应先销毁者释放内存两个对象中先被销毁的那个会在析构函数中delete/free这块内存幸存者持有悬垂指针后销毁的对象此时持有的指针指向已释放的内存任何后续解引用都是未定义行为dangling pointer / use-after-free后销毁者再次释放当第二个对象销毁时会对同一块内存再次执行delete/free构成 double-free同样是未定义行为。因此该检查器被归类为Undefined Behaviour类别尽管它通常以 Warning 级别上报——因为它在代码形态层面就能确认风险不需要等到运行时真正触发。源码级检测原理从“已分配成员”到“被拷贝成员”的追踪要理解 cppcheck 如何发现这个模式需要看 lib/checkclass.cpp 中copyconstructors()的两阶段算法。第一阶段收集“已分配内存”的指针成员检查器遍历类的所有构造函数从函数体的 token 流中匹配分配模式lib/checkclass.cppif (Token::Match(tok, %var% ( new) || (Token::Match(tok, %var% ( %name% () mSettings.library.getAllocFuncInfo(tok-tokAt(2)))) { const Variable* var tok-variable(); if (var var-scope() scope var-valueType() var-valueType()-type ! ValueType::SMART_POINTER) allocatedVars[tok-varId()] tok; }要点分配方式包括new也包括调用被cfglibrary 数据标记为分配函数如malloc、calloc、strdup等的调用判断依据是mSettings.library.getAllocFuncInfo()智能指针成员被显式排除ValueType::SMART_POINTER不登记因为智能指针自带所有权管理不属于裸指针浅拷贝风险只有类作用域内的普通非静态成员变量才被登记。第二阶段在复制构造函数中寻找浅拷贝随后检查器定位类中的复制构造函数func.type ! FunctionType::eCopyConstructor则跳过分两条路径扫描lib/checkclass.cpp初始化列表若复制构造函数的初始化列表中出现成员(源.成员)形式且该成员在allocatedVars中则记录为copiedVars——除非其后跟了%any% )形式的调用说明可能调用了分配函数构造函数体匹配%var% %name% . %name% ;即p f.p;且p在allocatedVars中记录为copiedVars反之若匹配到p new或p 分配函数(...)则视为已重新分配从allocatedVars中移除。最后只要复制构造函数存在且copiedVars非空就对其中的每一个被浅拷贝的成员上报一次copyCtorPointerCopyinglib/checkclass.cpp。值得注意的是源码中有一段被注释掉的、用于上报“复制构造函数未为已分配成员分配内存”的copyConstructorMallocError逻辑注释中标注了This doesnt work. See #4154lib/checkclass.cpp说明 cppcheck 目前在计数匹配上对部分边界场景仍采用保守策略这也与下方测试用例中大量TODO_ASSERT_EQUALS的现状一致。如何修复分配新内存并拷贝数据而不是拷贝指针修复思路很直接在复制构造函数中为指针成员分配一块全新内存再把源对象指针指向的数据拷贝进去而不是拷贝指针本身。修复前触发告警#include cstring #include cstdlib class F { char *p; F(const F f) { p f.p; // - both objects now share the same allocated block } public: F(char *str) { p malloc(strlen(str)1); } ~F(); F operator(const Ff); };上面的复制构造函数p f.p;就是典型的浅拷贝f与新对象共享f.p指向的那块堆内存两个对象销毁时会对同一块内存释放两次。修复后深拷贝#include cstring #include cstdlib class F { char *p; public: F(const F f) { p (char*)malloc(strlen(f.p)1); strcpy(p, f.p); } F(char *str) { p (char*)malloc(strlen(str)1); strcpy(p, str); } ~F(); F operator(const Ff); };修复后每个对象拥有自己独立的堆内存复制构造函数用malloc(strlen(f.p)1)分配新块再用strcpy拷贝数据两个对象销毁时各自释放各自的内存不再互相干扰。进阶建议优先考虑 RAII 容器/智能指针如果成员改为std::string、std::vector或std::unique_ptr等自带正确拷贝语义的类型编译器生成的复制构造函数本身就是正确的可以完全避免手写带来的浅拷贝风险对称修复复制构造函数浅拷贝的类其拷贝赋值运算符operator往往也存在同样问题自赋值、资源泄漏等建议一并审查结合规则如果类分配了资源却根本没有定义复制构造函数编译器生成版本同样是浅拷贝cppcheck 由另一条相关检查器 noCopyConstructor 负责捕获见下文。测试验证检查器的行为边界仓库的单元测试位于 test/testclass.cppcopyConstructor1()测试用例直接印证了检查器的判定逻辑复制构造函数在初始化列表p(f.p)浅拷贝、但函数体内又用malloc重新分配的场景测试标注为TODO_ASSERT_EQUALS——预期行为与实际输出存在差异说明该场景当前版本仍可能漏报或产生额外消息复制构造函数体内执行p f.p;的经典场景测试期望输出[test.cpp:4:7]: (warning) Value of pointer p, which points to allocated memory, is copied in copy constructor instead of allocating new memory. [copyCtorPointerCopying]构造函数在初始化列表p(malloc(size))中完成分配、复制构造函数不再触碰p的场景断言输出为空——即不会误报类中只有const char *成员通过pstr;直接赋值、并未分配内存的场景断言输出为空——印证了“仅对指向已分配内存的指针”报错的设计。这些用例表明检查器在主路径复制构造函数函数体内直接浅拷贝已分配指针上判定稳定但对初始化列表内浅拷贝与函数体重新分配混用的边界组合仍存在TODO标注的已知缺口。与相关检查器的协同该检查器并不是孤立的它与CheckClass模块内“资源管理三件套”检查相互补充noCopyConstructorIDnoCopyConstructor类分配了资源构造函数new/malloc、析构函数delete/free但完全没有定义自己的复制构造函数或仅 default时告警。此时编译器生成的复制构造函数必然浅拷贝裸指针兄弟模式 noOperatorEq 与 noDestructor分别对应operator和析构函数缺失时的同类“分配资源但缺少特殊成员函数”问题。三者同源于 lib/checkclass.cpp 中allocatedVars/deallocatedVars的收集结果copyCtorPointerCopying处理的是“复制构造函数存在但写错了”的情况noCopyConstructor处理的是“复制构造函数压根不存在”的情况两条规则共同覆盖了裸指针资源类最常见的两类拷贝缺陷。使用提示该检查器属于warning严重级别默认在常规检查中启用在命令行使用 cppcheck 检查 C 代码时即可触发无需额外开启特定选项源码中在 lib/checkclass.cpp 有mSettings.severity.isEnabled(Severity::warning)的开关判断上报消息中的$symbol会被替换为实际被浅拷贝的成员名如p便于在报错列表中直接定位问题成员如需在团队中统一治理此类问题可将copyCtorPointerCopying与noCopyConstructor一并纳入 CI 的 cppcheck 检查清单并配合--suppress精确管理误报。赞分享开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载相关推荐如何让老旧Mac焕发新生OpenCore Legacy Patcher完整使用指南如何让老旧Mac焕发新生OpenCore Legacy Patcher完整使用指南 你是否有一台被苹果官方抛弃的旧Mac看着它性能尚可却无法升级最新系统O开发工具静态分析代码质量质量保障Nuestate 实战指南用 URL 驱动状态的轻量级状态管理方案Nuestate 实战指南用 URL 驱动状态的轻量级状态管理方案 Nuestate 是 Nue 生态中一款以 URL 优先URL first 为核心开发工具静态分析代码质量质量保障YOLOv8 区域计数实战基于 Ultralytics 的多区域可移动区域实时计数指南YOLOv8 区域计数实战基于 Ultralytics 的多区域可移动区域实时计数指南 导读 本文基于仓库中的 examples/YOLOv8 Region开发工具静态分析代码质量质量保障上一篇如何在三星电视上安装JellyfinJellyfin-tizen新手入门教程下一篇ChatLab 变更履历全解析从 AI 工具链、CLI/MCP 到 Docker 部署的演进路线创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表