常见的内存泄露情况汇总
就好像学习硬件不点亮一颗电容,一块MCU不能说自己学过硬件一样。
没写过内存泄露的代码,不能说自己学习过CPP。w(゚Д゚)w
那场景的内存泄露场景又有哪些?你踩过多少坑呢?
楼主把初学者常见的内存错误问题汇总了一下,希望对你有邦族
总的来说,内存问题只有三类:
问题类型 | 核心特征 | 主要后果 |
---|---|---|
内存泄漏 | 只分配,不释放(失去引用) | 内存被永久占用,资源耗尽 |
多重释放 | 一次分配,多次释放 | 立即导致未定义行为(通常崩溃) |
悬挂指针(野指针) | 使用已释放的内存 | 未定义行为(数据损坏、崩溃) |
刁钻点来说,只有两类: 内存泄露和未定义行为。这个分类属于见仁见智了。
内存泄露
1. 直接分配未释放
最基本的内存泄漏情况是分配了内存但忘记释放。
void memory_leak() {int* ptr = new int[100]; // 分配内存// 使用 ptr...// 忘记调用 delete[] ptr;
}
2. 异常导致的内存泄漏
当在 new 和 delete 之间发生异常时,delete 可能不会被执行。
void potential_leak() {int* ptr = new int[100];some_function_that_might_throw(); // 如果这里抛出异常delete[] ptr; // 这行不会执行
}
解决方案:使用 RAII 技术或智能指针
#include <memory>
void no_leak() {std::unique_ptr<int[]> ptr(new int[100]);some_function_that_might_throw(); // 即使抛出异常,ptr 也会自动释放内存
}
3. 构造函数中的资源分配未在析构函数中释放
class ResourceHolder {
private:int* data;
public:ResourceHolder() {data = new int[100]; // 在构造函数中分配}// 缺少析构函数 ~ResourceHolder() { delete[] data; }// 内存泄漏:当对象销毁时,data 指向的内存不会被释放
};
4. 缺少拷贝构造函数和拷贝赋值运算符(Rule of Three)
当类管理资源时,如果缺少适当的拷贝控制成员,可能会导致多重释放或内存泄漏。
class BadArray {
private:int* data;size_t size;
public:BadArray(size_t n) : size(n), data(new int[n]) {}~BadArray() { delete[] data; }// 缺少拷贝构造函数和拷贝赋值运算符// 当发生拷贝时,两个对象会指向同一块内存// 析构时会导致双重释放或内存泄漏
};
5.容器中的指针未正确清理
#include <vector>
void container_leak() {std::vector<int*> vec;for (int i = 0; i < 10; ++i) {vec.push_back(new int(i)); // 分配内存}// 使用 vec...// 忘记释放 vec 中的每个指针// 正确做法:在清除 vector 前先释放每个元素for (auto ptr : vec) {delete ptr;}vec.clear();
}
更好的解决方案:使用智能指针容器
#include <vector>
#include <memory>
void no_container_leak() {std::vector<std::unique_ptr<int>> vec;for (int i = 0; i < 10; ++i) {vec.push_back(std::make_unique<int>(i));}// 不需要手动释放,vector 清除时会自动释放所有 unique_ptr
}
双重释放/多重释放 (Double/Multiple Free)
定义:对同一块动态分配的内存释放了多次。
根本原因:多个指针指向同一块内存,且这些指针的生命周期管理混乱,导致同一块内存被多个不同的代码路径释放。
后果:立即导致未定义行为。最常见的表现是程序瞬间崩溃(如 glibc 检测到 "double free or corruption"错误),但也可能破坏内存管理器的数据结构,为安全漏洞埋下隐患。
示例:
void double_free() {int* ptr1 = new int(5);int* ptr2 = ptr1; // 两个指针指向同一块内存delete ptr1; // 第一次释放,OKdelete ptr2; // 第二次释放同一块内存 -> 双重释放,灾难性错误!
}
多指针造成的垂悬重复泄露问题
简单来说,就是一块自由内存的指针被多个对象持有,不能确定谁在什么时候释放这块内存。 可能已经被释放了,但是别的还持有,别的指针可能还会释放,造成多重释放
void unclear_ownership() {int* data = new int[100];process_data(data); // 这个函数应该负责释放吗?// 如果不应该,那么这里需要释放// 如果应该,那么这里不需要释放// 所有权不明确容易导致双重释放或内存泄漏
}
悬挂指针 (Dangling Pointer) 的使用
定义:指针指向的内存已经被释放,但指针本身仍然被使用(解引用或再次释放)。
根本原因:内存被释放后,指向它的指针没有被及时置空 (nullptr
)。
后果:未定义行为。读取可能得到垃圾数据,写入会破坏可能已被重新分配给他用的内存区域,导致程序行为诡异且难以调试。
示例:
void use_dangling_pointer() {int* ptr = new int(5);delete ptr; // 内存已释放*ptr = 10; // 错误!向已释放的内存写入 -> 未定义行为int value = *ptr; // 错误!从已释放的内存读取 -> 未定义行为
}
返回局部对象的指针/引用
// 错误示例1:返回局部变量的指针
int* create_int_dangerously() {int local_value = 42; // local_value 在栈上分配return &local_value; // 返回它的地址
} // 函数结束,local_value 的生命周期结束,其内存被回收// 错误示例2:返回局部变量的引用
int& create_int_ref_dangerously() {int local_value = 42;return local_value; // 返回它的引用
} // 函数结束,同样的问题发生void main() {int* dangling_ptr = create_int_dangerously();int& dangling_ref = create_int_ref_dangerously();// 此时 dangling_ptr 和 dangling_ref 都指向已经被回收的栈内存// 对它们进行任何操作(读取、写入)都是未定义行为 (Undefined Behavior)std::cout << *dangling_ptr << std::endl; // 可能输出垃圾值,也可能崩溃dangling_ref = 100; // 可能破坏程序状态,导致难以调试的错误
}
幸运的是,像 GCC 和 Clang 这样的现代编译器通常能检测到这种错误,并会发出类似 warning: address of stack memory associated with local variable 'local_value' returned
的警告。
// 方式二(现代C++最佳实践):返回智能指针,自动管理所有权
#include <memory>
std::unique_ptr<int> create_int_safely() {auto ptr = std::make_unique<int>(42); // 在堆上分配return ptr; // 转移所有权给调用者
}
void main() {auto safe_ptr = create_int_safely();// ... 使用 safe_ptr ...// 无需手动释放!main函数结束时,safe_ptr超出作用域会自动释放内存。
}
多指针造成的垂悬问题
简单来说,就是一块自由内存的指针被多个对象持有,不能确定谁在什么时候释放这块内存。
void unclear_ownership() {int* data = new int[100];process_data(data); // 这个函数应该负责释放吗?// 如果不应该,那么这里需要释放// 如果应该,那么这里不需要释放// 所有权不明确容易导致双重释放或内存泄漏
}
好了,就是这些,在以后楼主遇到更复杂的情况也会收录进来的。如果这对你有用,还请点赞,投币 收藏 关注 一波~ 这对我非常重要ヾ(•ω•`)o