继承与多态场景下内存安全释放问题及代码错误排查
问题分析与解决方案
核心错误原因
1. 构造函数中非法释放未初始化指针
A类的带参构造函数和复制构造函数里,一开始就调用了del_data(),但此时成员变量m_s是未初始化的随机值。del_data()里的if(m_s)判断可能误判为非空,进而调用free(m_s)释放野指针,直接触发内存访问异常。
2. 子类析构函数导致的双重释放
B类显式定义了析构函数并调用A::del_data(),而A类的析构是虚析构,当通过基类指针obj2执行delete时,会先跑B的析构函数,再自动调用A的析构函数。这就导致del_data()被连续调用两次:
- 第一次在B析构中释放
m_s并置为nullptr - 第二次在A析构中虽不会重复执行
free,但属于完全多余的操作,且后续逻辑变更时容易引发新问题。
修复后的代码
#include <iostream> #include <cstring> using namespace std; class A { private: char *m_s = nullptr; // 初始化指针为nullptr,从根源避免野指针 public: A() { m_s = strdup("default"); } A(char *s) { m_s = strdup(s); } // 移除构造初期不必要的del_data()调用 A(const A& a) { m_s = strdup(a.m_s); } // 同上 virtual void prepare() { cout << "A "; } void display() { prepare(); cout << m_s << endl; } void del_data() { if (m_s) { free(m_s); m_s = nullptr; } } virtual ~A() { del_data(); } }; class B : public A { public: B(char *s) : A(s) { } B(const B &b) : A(b){} void prepare() { cout << "B "; } // 移除多余的析构函数,基类虚析构会自动完成资源清理 }; void foo(A *obj1, A obj2) { obj1->display(); obj2.display(); } int main() { B obj1("text"); A *obj2 = new B(obj1); foo(&obj1, *obj2); delete obj2; // 现在会正确触发B的析构(空实现),再调用A的析构释放资源 return 0; }
避免此类问题的要点
- 指针成员必须初始化:所有指针成员变量要在声明时或构造函数初始化列表中设为
nullptr,杜绝野指针引发的非法内存操作。 - 构造函数只做初始化:构造函数的职责是创建、初始化资源,不要在构造初期调用释放函数。
- 子类析构按需定义:如果子类没有新增需要手动释放的资源(比如动态内存、文件句柄),不用显式写析构函数,基类的虚析构会自动处理资源清理。
- 虚析构的正确使用:当需要通过基类指针删除子类对象时,基类必须声明虚析构,确保析构链(子类→基类)被正确调用。
- 内存操作严格匹配:
strdup分配的内存用free释放,new分配的用delete,new[]对应delete[],绝不混用。
内容的提问来源于stack exchange,提问作者Juno Lee
相关产品推荐
相关产品推荐

