C++中的内存泄漏和损坏
这是困扰程序员的两个重大问题,其本质都是因为:指针。
指针
我们都知道,指针是“保存某块内存地址的变量”。
1 | int a = 10; |
通过 *p 可以访问这块地址中的内容:
1 | cout << *p << endl; // 10 |
为什么指针危险?
因为指针只知道“地址”,它并不天然知道:这块内存是不是还有效、这块内存有多大、该不该由你释放、你是不是越界了,所以一旦管理不当,就会产生各种 bug。
为什么会出问题?
C++ 很重视“对象的生命周期”。比如:
1 | int* p = new int(5); |
如果销毁后还使用:cout << *p << endl; 这是未定义的行为,是典型的危险场景,因为:指针还在,不代表对象还在。
什么是内存泄漏?
定义:申请了内存,但后来再也没有办法释放它。
最简单例子:
1 | void f() { |
问题在哪里?
new int(100) 在堆上申请了一块内存,函数结束时,局部变量 p 被销毁,但那块堆内存没人再能找到,于是这块内存泄漏了。
正确做法:在最后加一句delete p;
数组泄漏:
1 | int* arr = new int[100]; |
常见泄漏场景
忘记
delete同上指针被覆盖
1
2
3int* p = new int(10);
p = new int(20); // 原来的那块内存地址丢了
delete p;第一次 new 出来的内存泄漏了
异常导致没走到释放代码
1
2
3
4
5void f() {
int* p = new int[100];
throw runtime_error("error");
delete[] p; // 永远执行不到
}容器里放裸指针但没清理
1
2
3vector<int*> v;
v.push_back(new int(1));
v.push_back(new int(2));如果程序结束前没逐个 delete,就泄漏。
循环里不断分配
1
2
3while (true) {
new int[1000];
}这会持续吃内存,最终可能把系统拖垮。
什么是内存损坏(Memory Corruption)
“内存泄漏”是没释放。
“内存损坏”是把不该写的地方写坏了,或者把内存管理状态弄乱了。
这是更危险的一类问题,因为它往往会:随机崩溃、表现不稳定、在别处报错、很难定位。
常见的内存损坏
- 越界访问(buffer overflow / out-of-bounds)
1 | int* arr = new int[3]; |
arr[3] 已经超出合法范围。后果可能是:改坏相邻变量、改坏堆管理器元数据、之后 delete[] 时崩溃。
释放后使用(use-after-free)
1
2
3int* p = new int(10);
delete p;
*p = 20; // 已释放后继续写这块内存已经不属于你了,再写就是未定义行为。
重复释放(double free)
1
2
3int* p = new int(10);
delete p;
delete p; // 重复释放第一次释放后,这块内存已经回收了。
第二次再释放,会破坏堆分配器的内部状态。释放方式不对
1
2int* arr = new int[10];
delete arr; // 错误,应为 delete[]或者
1
2
3int x;
int* p = &x;
delete p; // 错误,p 指向栈内存野指针(wild pointer)
1
2int* p;
*p = 10; // p 指向哪里根本不知道这是非常危险的。
悬空指针(dangling pointer)
1
2int* p = new int(5);
delete p; // p 还保存着旧地址,但对象已经不存在p 就成了悬空指针。
常见缓解手法:1
2delete p;
p = nullptr;这样再误用时更容易暴露问题。
为什么内存损坏经常“不是当场炸”
例如:
1 | int* arr = new int[3]; |
你以为会在 arr[100] = 1 这里马上崩。
但实际可能:当场没事、过几分钟才崩、在完全无关的地方崩、只在某台机器上崩
原因是:
C++ 对这些错误大多属于“未定义行为(Undefined Behavior)”。
意思是标准不保证任何结果。
你可能改坏的是:其他变量、堆分配器管理信息、虚函数表、返回地址附近内容(某些情况下)、所以症状常常延后出现。
未定义行为(UB)
C++ 里内存错误大多都属于 UB。
典型 UB 包括:越界访问、释放后使用、重复释放、解引用空指针、解引用未初始化指针
UB 的含义不是“结果随机”这么简单,而是:
- 编译器可以做任何事
- 程序可能看起来正常
- 也可能崩溃
- 也可能产生隐蔽错误
所以不能靠“我跑了一下没问题”来证明代码安全