C++不同类存储同一指针重复删除引发Heap corruption问题求解
问题场景
类A与类B共同存储指向类C实例的指针,两个类的析构函数中均会调用delete释放持有的C类指针。
异常表现
- 仅销毁A类实例或仅销毁B类实例时不会触发运行问题
- 当指向同一C实例的A、B类实例先后被销毁时,会抛出异常触发Heap corruption(堆损坏)
已尝试方案
在析构函数执行delete操作后,将当前类自身存储的C指针置为NULL,同时在释放前添加NULL判断逻辑尝试规避重复释放,但该逻辑未生效,问题依然存在。
当前实现代码如下(存在变量名笔误,成员变量为小写c,判断逻辑误写为大写C,修复笔误后重复释放问题仍存在):
class A{ C* c; } ~A(){ if(c != NULL){ delete c; c = NULL; } } class B{ C* c; } ~B(){ if(c != NULL){ delete c; c = NULL; } }
补充说明:已显式删除C类的拷贝构造函数和赋值运算符,避免因对象拷贝导致两个类存储的指针不一致。
核心疑问
- 已知共享所有权场景应使用
shared_ptr管理指针,但担忧需要将代码库中所有C类对象的指针全量替换为shared_ptr,改动成本过高,是否可以仅在A、B类存储该共享指针的位置使用shared_ptr,无需修改其余代码? - 除了显式删除C类拷贝构造和赋值运算符之外,是否还有其他防止意外拷贝的方案?
问题解答
置空指针失效的根本原因
A和B各自持有的c是两个完全独立的指针变量,二者只是恰好存储了同一个C实例的内存地址。A析构时释放的是地址指向的堆内存,随后仅把A自身的c变量修改为NULL,这个操作完全不会改动B实例中c变量存储的地址值。B析构时检测到自身持有的c仍为非空值,就会对已经释放的内存再次执行delete,最终触发堆损坏。
关于shared_ptr的改动范围
完全不需要全量替换所有C类指针,仅修改A、B类中持有共享C指针的成员即可,改动成本极低:
- 将A、B类中的成员定义从
C* c替换为std::shared_ptr<C> c - 初始化C实例时直接用
std::make_shared<C>()创建托管的C对象,将该shared_ptr分别赋值给A、B实例的c成员即可 - 其余所有接收
C*类型参数的原有代码,传参时调用shared_ptr的get()方法获取原始C指针传入即可,不需要修改其他模块的逻辑
关键约束:绝对不要用同一个原始C指针分别构造两个独立的shared_ptr,否则两个shared_ptr会各自维护独立的引用计数块,最终仍会触发重复释放。所有持有该C实例的shared_ptr必须从同一个最初的shared_ptr实例拷贝而来。
其他防止意外拷贝的方案
除了显式用= delete删除C类的拷贝构造、赋值运算符外,可根据场景选择以下方案:
- 兼容C++11之前版本的方案:将C类的拷贝构造函数、赋值运算符声明为
private权限且不提供实现,外部尝试拷贝C实例会直接触发编译错误,类内部/友元误调用会触发链接错误,效果和=delete一致 - 防控A、B类的拷贝风险:使用shared_ptr持有C指针后,A、B类的默认拷贝构造、赋值运算符会自动正确维护shared_ptr的引用计数,无需手动编写额外逻辑;如果不希望A、B实例被拷贝,也可以直接为A、B类添加
=delete的拷贝控制 - 非所有权持有场景:其他模块如果只需要访问C对象、不需要负责对象生命周期管理,可以直接使用原始指针传递,或用
std::weak_ptr持有,weak_ptr可在访问前校验对象是否已释放,避免野指针,且不会增加引用计数。
内容的提问来源于stack exchange,提问作者stackUnderflow
相关产品推荐
相关产品推荐

