C++ Valgrind检测内存泄漏:16个内存块仅11个被释放排查
问题根因
你自己加析构反而触发更多错误,和现有泄漏是同一套问题链导致的,三个泄漏点对应问题如下:
- 核心诱因:基类
MemberParliament没有定义虚析构函数。Minister作为公有继承自MemberParliament的派生类,当你通过基类指针/引用操作派生类对象时,非虚的析构函数会导致对象销毁时只执行基类析构逻辑,派生类持有的堆资源完全不会被释放,直接触发未定义行为,这就是你加了析构就出崩溃/报错的核心原因。 - 三个明确泄漏点的对应原因:
MemberParliament构造函数泄漏:你在构造函数里用new给类内的char*类型成员(比如议员姓名、所属选区字符串)分配了堆内存,但既没有在析构函数中释放,也没有实现深拷贝逻辑,对象生命周期结束时这部分内存直接丢失。MemberParliament::NewDistrict函数泄漏:这个函数用来更新议员所属选区时,你直接给存储选区名的指针成员new了新内存写入新选区名,但没有提前释放指针之前指向的旧选区名堆内存,每调用一次就泄漏一块内存。Minister::changePM函数泄漏:逻辑和NewDistrict完全一致,更新总理名称时,直接覆盖了存储PM姓名的指针成员,没有释放之前存旧PM姓名的堆内存,每次调用产生一块泄漏。
- 额外踩坑:没有遵守C类设计的三/五法则。只要类需要自定义析构函数来释放资源,就必须同时实现拷贝构造函数、拷贝赋值运算符(C11及以后还要考虑移动构造、移动赋值),否则编译器默认生成的浅拷贝逻辑会让多个对象的指针成员指向同一块堆内存,对象销毁时就会触发double free错误,这也是你加析构后报错更多的另一个原因。
修复步骤
按顺序修改即可,不要跳步:
- 给基类添加虚析构
在MemberParliament.h的类定义public区域添加虚析构声明:
在virtual ~MemberParliament();MemberParliament.cpp中实现该析构函数,对应释放构造函数中new出来的所有堆资源,注意new[]分配的数组必须用delete[]释放,普通new分配的单个对象用delete释放,不要混用。 - 修复
NewDistrict函数的内存泄漏
给选区名指针分配新内存前,先释放旧内存,参考逻辑如下,把变量名替换成你代码里的实际命名:void MemberParliament::NewDistrict(const char* newDist) { // 先释放旧的选区名内存 delete[] district; // 再分配新内存存储新名称 district = new char[strlen(newDist) + 1]; strcpy(district, newDist); } - 修复
changePM函数的内存泄漏
和上面NewDistrict的逻辑完全一致,在给存储PM姓名的指针成员分配新内存前,先释放指针指向的旧PM名称内存,不要直接覆盖指针值。 - 补全拷贝控制逻辑,避免浅拷贝错误
针对类内所有堆分配的指针成员,实现深拷贝版本的拷贝构造、拷贝赋值运算符,避免多个对象共享同一块堆内存导致重复释放。拷贝赋值的实现要注意先判断自赋值场景,参考逻辑:
派生类// 拷贝构造 MemberParliament::MemberParliament(const MemberParliament& other) { // 为当前对象的字符串成员单独分配内存,拷贝内容 name = new char[strlen(other.name) + 1]; strcpy(name, other.name); district = new char[strlen(other.district) + 1]; strcpy(district, other.district); // 其余成员按同样逻辑处理 } // 拷贝赋值 MemberParliament& MemberParliament::operator=(const MemberParliament& other) { if (this == &other) return *this; // 处理自赋值 // 释放当前对象持有的旧内存 delete[] name; delete[] district; // 深拷贝新内容 name = new char[strlen(other.name) + 1]; strcpy(name, other.name); district = new char[strlen(other.district) + 1]; strcpy(district, other.district); return *this; }Minister中独有的堆成员(比如存储PM姓名的指针),也要在自己的析构、拷贝构造、拷贝赋值中实现对应的释放和深拷贝逻辑。 - 验证修复效果
编译时加-g -O0参数保留调试符号,重新编译后用Valgrind检测:
最终Valgrind输出g++ -g -O0 main.cpp MemberParliament.cpp Minister.cpp -o mp_test valgrind --leak-check=full ./mp_testAll heap blocks were freed -- no leaks are possible即修复完成。
优化建议:如果没有强制要求使用裸
new和char*,直接用std::string存储所有姓名字符串内容,不需要手动管理内存,上面提到的90%内存问题会直接消失,也不需要手写一堆析构、拷贝控制函数,代码可维护性高很多。
内容的提问来源于stack exchange,提问作者Gemine Jen
相关产品推荐
相关产品推荐

