C++作为一门支持手动内存管理的编程语言,开发者需要自行控制动态内存的申请和释放,这也使得内存泄漏成为C++项目中高发的问题。内存泄漏指的是程序动态申请的内存在使用完毕后没有被正确释放,导致这部分内存无法被后续程序使用,长期运行会不断消耗系统内存资源。

C++内存泄漏的常见成因
1. 动态内存申请未释放
使用new或者new[]申请的内存,没有对应使用delete或者delete[]释放,是最基础的内存泄漏原因。比如下面的代码就会出现内存泄漏:
#include <iostream>
void test_leak() {
// 使用new申请了int类型的内存,没有释放
int* p = new int(10);
// 函数结束,p指针被销毁,指向的内存无法再被访问,也没有释放
}
int main() {
test_leak();
return 0;
}
2. 释放逻辑被异常中断
如果动态内存申请之后,释放内存的代码之前出现了异常,导致程序跳出了当前作用域,没有执行到释放逻辑,也会产生内存泄漏。例如:
#include <iostream>
#include <stdexcept>
void test_exception_leak() {
int* p = new int(20);
// 这里抛出异常,后续delete不会执行
throw std::runtime_error("发生异常");
delete p;
}
int main() {
try {
test_exception_leak();
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
3. 指针被重复赋值覆盖
当一个指针指向了新申请的内存,之前指向的旧内存地址没有被保存,也没有被释放,旧内存就会泄漏。比如:
#include <iostream>
void test_overwrite_leak() {
int* p = new int(30);
// p重新指向新的内存,原来的内存地址丢失,无法释放
p = new int(40);
delete p; // 只释放了新申请的内存,旧内存泄漏
}
int main() {
test_overwrite_leak();
return 0;
}
4. 基类析构函数未声明为虚函数
当使用基类指针指向派生类对象,并且通过delete基类指针释放内存时,如果基类析构函数不是虚函数,只会调用基类的析构函数,派生类的资源不会被释放,导致内存泄漏。示例代码如下:
#include <iostream>
class Base {
public:
// 析构函数不是虚函数
~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
int* data;
Derived() {
data = new int(50);
}
~Derived() {
delete data;
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base* p = new Derived();
// 只调用Base的析构函数,Derived的data没有释放
delete p;
return 0;
}
C++内存泄漏的检测方法
1. 代码层面手动排查
对于小型项目,可以通过梳理所有new和delete的配对情况来排查。可以自定义内存管理函数,在申请和释放内存时记录日志,方便后续追踪内存的使用情况。比如下面的简单示例:
#include <iostream>
#include <map>
std::map<void*, std::string> mem_record;
void* my_new(size_t size, const std::string& tag) {
void* p = malloc(size);
mem_record[p] = tag;
std::cout << "申请内存:" << p << ",标签:" << tag << std::endl;
return p;
}
void my_delete(void* p) {
auto it = mem_record.find(p);
if (it != mem_record.end()) {
std::cout << "释放内存:" << p << ",标签:" << it->second << std::endl;
mem_record.erase(it);
}
free(p);
}
int main() {
int* p1 = (int*)my_new(sizeof(int), "p1");
int* p2 = (int*)my_new(sizeof(int), "p2");
my_delete(p1);
// p2没有释放,最后输出记录可以看到未释放的内存
for (auto& item : mem_record) {
std::cout << "未释放内存:" << item.first << ",标签:" << item.second << std::endl;
}
return 0;
}
2. 使用智能指针规避泄漏
C++11之后引入了智能指针,通过RAII机制自动管理内存,不需要手动调用delete,可以从根源上减少内存泄漏的发生。常用的智能指针有std::unique_ptr和std::shared_ptr:
std::unique_ptr:独占所有权的智能指针,离开作用域会自动释放内存,不能拷贝只能移动。std::shared_ptr:共享所有权的智能指针,通过引用计数管理内存,当引用计数为0时自动释放。
使用智能指针改写之前的第一个泄漏示例:
#include <iostream>
#include <memory>
void test_no_leak() {
// 使用unique_ptr管理内存,函数结束自动释放
std::unique_ptr<int> p = std::make_unique<int>(10);
}
int main() {
test_no_leak();
return 0;
}
3. 借助第三方检测工具
对于大型项目,手动排查效率很低,可以使用专业的第三方工具检测内存泄漏,常用的工具包括:
| 工具名称 | 适用平台 | 特点 |
|---|---|---|
| Valgrind | Linux | 开源工具,不仅可以检测内存泄漏,还能检测内存越界、使用未初始化内存等问题,使用简单,只需要直接运行程序即可 |
| Visual Studio 诊断工具 | Windows | 集成在Visual Studio中,调试时可以直接查看内存泄漏的报告,定位泄漏的代码位置 |
| AddressSanitizer | 跨平台 | 编译器内置的检测工具,GCC和Clang都支持,编译时添加对应参数即可开启检测,性能开销较小 |
以Valgrind为例,检测内存泄漏的命令如下:
# 假设编译后的可执行文件名为test valgrind --leak-check=full ./test
运行后会输出详细的内存泄漏信息,包括泄漏的内存大小、对应的代码位置,方便开发者快速定位问题。
总结
C++内存泄漏的成因大多和手动内存管理的疏漏有关,开发者在编写代码时需要严格保证new和delete的配对,同时尽量使用智能指针替代裸指针管理动态内存。对于已经出现的内存泄漏问题,可以根据项目规模选择合适的检测方法,小型项目手动排查,大型项目借助第三方工具快速定位,从而有效解决内存泄漏问题,提升程序的运行稳定性。
C++内存泄漏new_delete智能指针Valgrind修改时间:2026-07-10 18:42:37