You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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类的拷贝构造函数和赋值运算符,避免因对象拷贝导致两个类存储的指针不一致。

核心疑问
  1. 已知共享所有权场景应使用shared_ptr管理指针,但担忧需要将代码库中所有C类对象的指针全量替换为shared_ptr,改动成本过高,是否可以仅在A、B类存储该共享指针的位置使用shared_ptr,无需修改其余代码?
  2. 除了显式删除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 15:48:45