c++内存 | 内存泄露 | 共享指针
一、什么是内存泄漏
1.1 概念图解
┌─────────────────────────────────────────────────────────────────┐│ 内存泄漏示意图 │├─────────────────────────────────────────────────────────────────┤│ ││ 程序开始 运行中 程序结束 ││ ↓ ↓ ↓ ││ ┌──────┐ ┌──────┐ ┌──────┐ ││ │ 堆 │ │ 堆 │ │ 堆 │ ││ │ 空 │ ──────► │ ████ │ ◄── 泄漏 │ ████ │ ◄── 无法释放││ └──────┘ │ ████ │ │ ████ │ ││ └──────┘ └──────┘ ││ ││ 分配内存 ✓ 使用内存 ✓ 释放内存 ✗ ││ │└─────────────────────────────────────────────────────────────────┘
1.2 指针重新赋值导致的内存泄漏
// 文件:memory_leak_demo.cppusing namespace std;void pointer_reassign_leak() {// 错误示例:指针重新赋值导致内存泄漏char *p = (char *)malloc(10);char *p1 = (char *)malloc(10);// 假设 p 指向地址 0x1000,p1 指向地址 0x2000p = p1; // ❌ p 原来指向的 0x1000 丢失了!内存泄漏!// 正确做法:先释放再赋值// free(p);// p = p1;}/*┌─────────┐ 分配前 分配后 重新赋值后│ │ ┌─────┐ ┌─────┐│ p │ ─────────► 0x1000│ 10B │ p ──►│ 10B │ (0x2000)│ │ └─────┘ └─────┘│ p1 │ ─────────► 0x2000┌─────┐ p1 ──►┌─────┐│ │ │ 10B │ │ 10B │ (0x2000)└─────────┘ └─────┘ └─────┘┌─────┐遗留 │ 10B │ ← 无法访问!└─────┘*/
1.3 常见内存泄漏场景
// 文件:common_leaks.cppusing namespace std;// 场景1:忘记释放void forget_release() {int* p = new int(100);// 使用 p...// ❌ 忘记 delete p;}// 场景2:异常导致无法释放void exception_leak() {int* p = new int[1000];// 如果这里抛出异常,delete 永远不会执行throw runtime_error("something wrong");delete[] p; // ❌ 永远不会执行}// 场景3:循环引用(后面详细讲)class Node {public:shared_ptr<Node> next;~Node() { cout << "Node destroyed" << endl; }};void cycle_ref_leak() {shared_ptr<Node> n1(new Node());shared_ptr<Node> n2(new Node());n1->next = n2;n2->next = n1; // ❌ 循环引用,两个都不会被释放}
二、防止内存泄漏的方法
2.1 RAII封装(构造时获取,析构时释放)
// 文件:raii_wrapper.cppusing namespace std;// 基础版本:有浅拷贝问题class SimpleRAII {private:char* p;unsigned int p_size;public:SimpleRAII(unsigned int n = 1) {p = new char[n];p_size = n;cout << "构造:分配内存" << endl;}~SimpleRAII() {if (p != NULL) {delete[] p;p = NULL;cout << "析构:释放内存" << endl;}}char* GetPointer() { return p; }};// 浅拷贝问题演示void shallow_copy_problem() {SimpleRAII ex(100);SimpleRAII ex1 = ex; // ❌ 浅拷贝!两个对象指向同一内存// 析构时会释放两次 → 程序崩溃}// 运行结果:构造:分配内存// 析构:释放内存// 析构:释放内存 ← 崩溃!// ==================== 正确版本:使用引用计数 ====================class RefCountRAII {private:char* p;unsigned int p_size;int* p_count; // 引用计数指针public:RefCountRAII(unsigned int n = 1) {p = new char[n];p_size = n;p_count = new int(1);cout << "构造:分配内存,count = " << *p_count << endl;}// 拷贝构造:引用计数+1RefCountRAII(const RefCountRAII& temp) {p = temp.p;p_size = temp.p_size;p_count = temp.p_count;(*p_count)++;cout << "拷贝构造:count = " << *p_count << endl;}// 赋值运算符RefCountRAII& operator=(const RefCountRAII& temp) {if (this != &temp) {// 先减少当前对象的引用计数if (--(*p_count) == 0) {delete[] p;delete p_count;cout << "释放旧内存" << endl;}// 指向新对象p = temp.p;p_size = temp.p_size;p_count = temp.p_count;(*p_count)++;cout << "赋值操作:count = " << *p_count << endl;}return *this;}~RefCountRAII() {(*p_count)--;cout << "析构:count = " << *p_count << endl;if (*p_count == 0) {delete[] p;delete p_count;cout << "释放内存 ✓" << endl;}}char* GetPointer() { return p; }};// 测试引用计数版本void test_ref_count() {cout << "n=== 测试引用计数RAII ===" << endl;RefCountRAII ex(100);strcpy(ex.GetPointer(), "Test");cout << "ex.p = " << ex.GetPointer() << endl;RefCountRAII ex1 = ex; // 拷贝构造,计数+1cout << "ex1.p = " << ex1.GetPointer() << endl;}// 输出:// 构造:分配内存,count = 1// ex.p = Test// 拷贝构造:count = 2// ex1.p = Test// 析构:count = 1// 析构:count = 0// 释放内存 ✓
三、智能指针详解
3.1 三种智能指针对比图
┌─────────────────────────────────────────────────────────────────────┐│ C++11 智能指针家族 │├─────────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────────────┐ ││ │ shared_ptr │ ││ │ ┌─────┐ ┌─────────────┐ ┌─────────┐ │ ││ │ │ ptr1│───►│ 控制块 │ │ 对象 │ │ ││ │ └─────┘ │ 引用计数: 2 │◄───┤ │ │ ││ │ ┌─────┐ │ 弱计数: 0 │ └─────────┘ │ ││ │ │ ptr2│───►└─────────────┘ │ ││ │ └─────┘ │ ││ │ 特点:多个指针共享所有权,引用计数归零时释放 │ ││ └─────────────────────────────────────────────────────────────┘ ││ ││ ┌─────────────────────────────────────────────────────────────┐ ││ │ unique_ptr │ ││ │ ┌─────┐ ┌─────────┐ │ ││ │ │ ptr │────────►│ 对象 │ │ ││ │ └─────┘ └─────────┘ │ ││ │ 特点:独占所有权,不能拷贝,只能移动 │ ││ └─────────────────────────────────────────────────────────────┘ ││ ││ ┌─────────────────────────────────────────────────────────────┐ ││ │ weak_ptr │ ││ │ ┌─────┐ ┌─────────────┐ ┌─────────┐ │ ││ │ │ wptr│───►│ 控制块 │ │ 对象 │ │ ││ │ └─────┘ │ 引用计数: 1 │◄───┤ │ │ ││ │ │ 弱计数: 1 │ └─────────┘ │ ││ │ └─────────────┘ │ ││ │ 特点:弱引用,不增加引用计数,用于打破循环引用 │ ││ └─────────────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────┘
3.2 智能指针完整实现(参考源码)
// 文件:smart_ptr_implementation.cppusing namespace std;template<typename T>class SharedPtr {private:T* _ptr;size_t* _count; // 引用计数public:// 构造函数explicit SharedPtr(T* ptr = nullptr) : _ptr(ptr) {_count = new size_t(ptr ? 1 : 0);cout << "SharedPtr构造,引用计数 = " << *_count << endl;}// 拷贝构造函数SharedPtr(const SharedPtr<T>& other) {_ptr = other._ptr;_count = other._count;(*_count)++;cout << "SharedPtr拷贝构造,引用计数 = " << *_count << endl;}// 赋值运算符SharedPtr<T>& operator=(const SharedPtr<T>& other) {if (this != &other) {// 释放当前资源release();// 接管新资源_ptr = other._ptr;_count = other._count;(*_count)++;cout << "SharedPtr赋值,引用计数 = " << *_count << endl;}return *this;}// 移动构造函数SharedPtr(SharedPtr<T>&& other) noexcept {_ptr = other._ptr;_count = other._count;other._ptr = nullptr;other._count = nullptr;cout << "SharedPtr移动构造" << endl;}// 析构函数~SharedPtr() {release();}void release() {if (_count) {(*_count)--;cout << "SharedPtr析构,引用计数 = " << *_count << endl;if (*_count == 0) {delete _ptr;delete _count;cout << "释放内存 ✓" << endl;}}}T& operator*() const {assert(_ptr != nullptr);return *_ptr;}T* operator->() const {assert(_ptr != nullptr);return _ptr;}T* get() const { return _ptr; }size_t use_count() const { return _count ? *_count : 0; }bool unique() const { return use_count() == 1; }};// 测试自定义SharedPtrclass Test {public:int value;Test(int v = 0) : value(v) {cout << "Test对象构造: " << value << endl;}~Test() {cout << "Test对象析构: " << value << endl;}};void test_custom_shared_ptr() {cout << "n===== 测试自定义SharedPtr =====" << endl;SharedPtr<Test> sp1(new Test(100));cout << "sp1.use_count() = " << sp1.use_count() << endl;{SharedPtr<Test> sp2 = sp1; // 拷贝构造cout << "sp2.use_count() = " << sp2.use_count() << endl;cout << "sp1->value = " << sp1->value << endl;cout << "(*sp2).value = " << (*sp2).value << endl;} // sp2析构,引用计数减1cout << "sp1.use_count() = " << sp1.use_count() << endl;} // sp1析构,引用计数变0,释放内存// ==================== UniquePtr实现 ====================template<typename T>class UniquePtr {private:T* _ptr;public:explicit UniquePtr(T* ptr = nullptr) : _ptr(ptr) {cout << "UniquePtr构造" << endl;}// 禁止拷贝UniquePtr(const UniquePtr<T>&) = delete;UniquePtr<T>& operator=(const UniquePtr<T>&) = delete;// 允许移动UniquePtr(UniquePtr<T>&& other) noexcept : _ptr(other._ptr) {other._ptr = nullptr;cout << "UniquePtr移动构造" << endl;}UniquePtr<T>& operator=(UniquePtr<T>&& other) noexcept {if (this != &other) {delete _ptr;_ptr = other._ptr;other._ptr = nullptr;cout << "UniquePtr移动赋值" << endl;}return *this;}~UniquePtr() {delete _ptr;cout << "UniquePtr析构,释放内存" << endl;}T& operator*() const { return *_ptr; }T* operator->() const { return _ptr; }T* get() const { return _ptr; }T* release() {T* tmp = _ptr;_ptr = nullptr;return tmp;}void reset(T* ptr = nullptr) {delete _ptr;_ptr = ptr;}};
3.3 智能指针使用示例
// 文件:smart_ptr_usage.cppusing namespace std;// ==================== shared_ptr 使用 ====================void shared_ptr_demo() {cout << "n===== shared_ptr 演示 =====" << endl;// 创建 shared_ptrshared_ptr<int> sp1 = make_shared<int>(42);shared_ptr<int> sp2(sp1); // 拷贝构造,引用计数+1shared_ptr<int> sp3 = sp1; // 赋值,引用计数+1cout << "sp1 use_count: " << sp1.use_count() << endl; // 3cout << "sp2 use_count: " << sp2.use_count() << endl; // 3cout << "*sp1 = " << *sp1 << endl;sp2.reset(); // 释放sp2,引用计数-1cout << "after reset sp2, use_count: " << sp1.use_count() << endl; // 2}// ==================== unique_ptr 使用 ====================void unique_ptr_demo() {cout << "n===== unique_ptr 演示 =====" << endl;unique_ptr<int> up1(new int(100));// unique_ptr<int> up2 = up1; // ❌ 编译错误,不能拷贝unique_ptr<int> up2 = move(up1); // ✓ 移动语义,所有权转移if (up1 == nullptr) {cout << "up1 现在是空指针" << endl;}cout << "*up2 = " << *up2 << endl;// 工厂模式常用auto make_resource = []() {return make_unique<string>("Hello World");};unique_ptr<string> up3 = make_resource();cout << "*up3 = " << *up3 << endl;}// ==================== weak_ptr 使用 ====================void weak_ptr_demo() {cout << "n===== weak_ptr 演示 =====" << endl;shared_ptr<int> sp = make_shared<int>(999);weak_ptr<int> wp = sp; // 弱引用,不增加引用计数cout << "sp use_count: " << sp.use_count() << endl; // 1cout << "wp expired: " << wp.expired() << endl; // false// 使用 weak_ptr 前需要 lock() 转换为 shared_ptrif (auto locked = wp.lock()) {cout << "通过 weak_ptr 访问: " << *locked << endl;}sp.reset(); // 释放共享指针cout << "after reset, wp expired: " << wp.expired() << endl; // trueif (wp.lock() == nullptr) {cout << "weak_ptr 指向的对象已被销毁" << endl;}}// ==================== 循环引用问题与解决 ====================class Child; // 前向声明class Parent {public:shared_ptr<Child> child_ptr;// weak_ptr<Child> child_ptr; // ✅ 解决方案:改用 weak_ptr~Parent() { cout << "Parent 析构" << endl; }};class Child {public:shared_ptr<Parent> parent_ptr;// weak_ptr<Parent> parent_ptr; // ✅ 解决方案~Child() { cout << "Child 析构" << endl; }};void cycle_reference_problem() {cout << "n===== 循环引用问题 =====" << endl;shared_ptr<Parent> parent(new Parent());shared_ptr<Child> child(new Child());parent->child_ptr = child; // Parent 持有 Childchild->parent_ptr = parent; // Child 持有 Parent// 引用计数都是2,离开作用域时都只减到1,永远不会为0// 导致内存泄漏!两个对象的析构函数都不会被调用}void cycle_reference_fixed() {cout << "n===== 使用 weak_ptr 解决循环引用 =====" << endl;class ParentFixed {public:weak_ptr<Child> child_ptr; // ✅ 使用 weak_ptr~ParentFixed() { cout << "ParentFixed 析构" << endl; }};class ChildFixed {public:shared_ptr<ParentFixed> parent_ptr;~ChildFixed() { cout << "ChildFixed 析构" << endl; }};shared_ptr<ParentFixed> parent(new ParentFixed());shared_ptr<ChildFixed> child(new ChildFixed());parent->child_ptr = child;child->parent_ptr = parent;cout << "parent use_count: " << parent.use_count() << endl; // 1cout << "child use_count: " << child.use_count() << endl; // 2 (因为 Child 用 shared_ptr 持有 Parent)// 实际上最好两边都用 weak_ptr,根据实际业务需求决定}// ==================== 最佳实践:使用 make_shared ====================void best_practices() {cout << "n===== 最佳实践 =====" << endl;// ✅ 推荐:使用 make_shared(一次分配,更高效)auto sp1 = make_shared<int>(42);auto sp2 = make_shared<string>("Hello");// ✅ 自定义删除器auto file_deleter = [](FILE* f) {if (f) {fclose(f);cout << "文件已关闭" << endl;}};// 管理C风格文件指针unique_ptr<FILE, decltype(file_deleter)> file_ptr(fopen("test.txt", "w"), file_deleter);// ❌ 不推荐:直接使用 new(可能造成异常安全问题)// shared_ptr<int> sp3(new int(10)); // 可能导致内存泄漏// ✅ 使用 vector 存储智能指针vector<shared_ptr<int>> vec;vec.push_back(make_shared<int>(1));vec.push_back(make_shared<int>(2));vec.push_back(make_shared<int>(3));}
3.4 unique_ptr 移动赋值详解
// 文件:unique_ptr_move.cppusing namespace std;class Resource {public:int id;Resource(int i) : id(i) {cout << "Resource " << id << " 创建" << endl;}~Resource() {cout << "Resource " << id << " 销毁" << endl;}};void unique_ptr_move_demo() {cout << "n===== unique_ptr 移动赋值详解 =====" << endl;// 创建两个 unique_ptrunique_ptr<Resource> up1(new Resource(1));unique_ptr<Resource> up2(new Resource(2));cout << "up1 指向 Resource " << up1->id << endl;cout << "up2 指向 Resource " << up2->id << endl;// 转移所有权:up1 的所有权转移给 up2// 原来的 up2 指向的资源会被释放up2 = move(up1);cout << "n=== 移动后 ===" << endl;if (up1 == nullptr) {cout << "up1 现在是空指针" << endl;}if (up2 != nullptr) {cout << "up2 现在指向 Resource " << up2->id << endl;}}/*输出:Resource 1 创建Resource 2 创建up1 指向 Resource 1up2 指向 Resource 2Resource 2 销毁 ← up2 原来的资源被释放=== 移动后 ===up1 现在是空指针up2 现在指向 Resource 1Resource 1 销毁 ← 程序结束时释放*/
3.5 共享指针避坑
// C++ 智能指针 核心坑点 + 注意事项(浓缩可直接运行参考)using namespace std;// 辅助测试类class Test {public:Test() { cout << "Test 构造n"; }~Test() { cout << "Test 析构n"; }void show() { cout << "Test::show()n"; }};// 辅助函数:错误示范:不要返回局部对象的 unique_ptr// unique_ptr<Test> bad_return_local() {// Test t;// return make_unique<Test>(&t); // 野指针!// }int main() {// ==================== 1. unique_ptr 核心坑 ====================// ✅ 正确:make_unique 初始化unique_ptr<Test> u1 = make_unique<Test>();u1->show();// ❌ 错误:不能拷贝,只能移动// unique_ptr<Test> u2 = u1; // 编译报错unique_ptr<Test> u2 = move(u1); // ✅ 必须 moveif (!u1) cout << "u1 已失效,所有权转移n";// ❌ 错误:同一裸指针构造多个 unique_ptr → 双重释放Test* p = new Test();unique_ptr<Test> u3(p);// unique_ptr<Test> u4(p); // 崩溃!同一块内存被两个智能指针管理// ==================== 2. shared_ptr 核心坑 ====================// ✅ 正确:make_shared(推荐,效率高、安全)shared_ptr<Test> s1 = make_shared<Test>();cout << "引用计数:" << s1.use_count() << endl;shared_ptr<Test> s2 = s1;cout << "引用计数:" << s1.use_count() << endl;// ❌ 错误:用裸指针初始化多个 shared_ptr → 双重释放Test* p2 = new Test();shared_ptr<Test> s3(p2);// shared_ptr<Test> s4(p2); // 崩溃!两个独立控制块// ❌ 致命坑:循环引用 → 内存泄漏struct Node { shared_ptr<Node> next; };auto node1 = make_shared<Node>();auto node2 = make_shared<Node>();node1->next = node2;node2->next = node1;// 出作用域不会析构!必须用 weak_ptr 打破循环// ==================== 3. weak_ptr 核心坑 ====================weak_ptr<Test> w1 = s1;if (!w1.expired()) {shared_ptr<Test> tmp = w1.lock(); // ✅ 必须 lock 才能访问tmp->show();}// ❌ 错误:直接使用 weak_ptr 访问对象 → 编译报错// w1->show();// ==================== 4. 通用致命坑 ====================// ❌ 不要管理栈内存 → 重复释放崩溃// Test stack_obj;// shared_ptr<Test> s_bad(&stack_obj);// ❌ 不要 delete 智能指针管理的裸指针 → 悬空/崩溃Test* p3 = new Test();shared_ptr<Test> s5(p3);// delete p3; // 智能指针析构时再次 delete// ❌ 不要混用 new / new[] → 匹配 unique_ptr<T[]>unique_ptr<int[]> arr_u = make_unique<int[]>(5); // ✅ 数组专用// unique_ptr<int> arr_bad(new int[5]); // 错误,析构用 delete 而非 delete[]// ==================== 5. 最佳实践 ====================// ✅ 优先:make_unique / make_shared,避免裸指针// ✅ 优先:unique_ptr > shared_ptr(性能更高)// ✅ 循环引用必须用 weak_ptr// ✅ 不要手动管理裸指针 + 智能指针混用// ✅ 不要返回局部对象指针/引用// ✅ 多线程:shared_ptr 引用计数安全,但对象访问不安全return 0;}
四、内存泄漏检测工具原理(valgrind)
4.1 valgrind 工作原理图
┌─────────────────────────────────────────────────────────────────────┐│ Valgrind 检测原理 │├─────────────────────────────────────────────────────────────────────┤│ ││ 原始程序 Valgrind 中运行 ││ ┌─────────┐ ┌─────────────────────────────┐ ││ │ 代码 │ │ Valgrind Core │ ││ └────┬────┘ │ ┌───────────────────────┐ │ ││ │ │ │ Valid-Value 表 │ │ ││ ▼ │ │ 每个字节对应8 bits │ │ ││ ┌─────────┐ │ │ 记录值是否已初始化 │ │ ││ │ CPU执行 │ │ └───────────────────────┘ │ ││ └─────────┘ │ ┌───────────────────────┐ │ ││ │ │ Valid-Address 表 │ │ ││ │ │ 每个字节对应1 bit │ │ ││ │ │ 记录地址是否可读写 │ │ ││ │ └───────────────────────┘ │ ││ │ │ ││ │ ┌───────────────────────┐ │ ││ │ │ 模拟CPU │ │ ││ │ │ 检查每次内存操作 │ │ ││ │ └───────────────────────┘ │ ││ └─────────────────────────────┘ ││ ││ 检测到的问题: ││ • 使用未初始化的内存 ││ • 内存越界读写 ││ • 内存泄漏 ││ • 重复释放 ││ │└─────────────────────────────────────────────────────────────────────┘
4.2 使用 valgrind 检测内存泄漏
# 编译程序(需要包含调试信息)g++ -g -o myprogram myprogram.cpp# 使用 valgrind 检测valgrind --leak-check=full --show-leak-kinds=all ./myprogram# 常用选项valgrind --tool=memcheck --leak-check=full --track-origins=yes ./myprogram
// 文件:valgrind_test.cppusing namespace std;// 一个有内存泄漏的程序void leaky_function() {int* p = new int[100];// 忘记 delete[] p}void use_uninitialized() {int* p = (int*)malloc(sizeof(int));// 未初始化就使用cout << *p << endl; // 使用未初始化的值free(p);}void double_free() {int* p = new int(10);delete p;delete p; // 重复释放}int main() {leaky_function();use_uninitialized();// double_free(); // 取消注释会触发错误return 0;}
五、快速参考卡片
⚠️ 版权声明
本文为免费文章,欢迎转载。转载请注明出处:一木科技(https://www.pingquan.vip/2026/05/31/c%e5%86%85%e5%ad%98-%e5%86%85%e5%ad%98%e6%b3%84%e9%9c%b2-%e5%85%b1%e4%ba%ab%e6%8c%87%e9%92%88/),并保留原文链接。
还没有评论,来说两句?