c++内存 | 内存泄露 | 共享指针

文章工具

一、什么是内存泄漏

1.1 概念图解

┌─────────────────────────────────────────────────────────────────┐│                        内存泄漏示意图                            │├─────────────────────────────────────────────────────────────────┤│                                                                 ││   程序开始              运行中                程序结束          ││      ↓                    ↓                      ↓             ││   ┌──────┐            ┌──────┐               ┌──────┐          ││   │ 堆   │            │ 堆   │               │ 堆   │          ││   │ 空   │  ──────►   │ ████ │ ◄── 泄漏      │ ████ │ ◄── 无法释放││   └──────┘            │ ████ │               │ ████ │          ││                       └──────┘               └──────┘          ││                                                                 ││   分配内存 ✓              使用内存 ✓              释放内存 ✗    ││                                                                 │└─────────────────────────────────────────────────────────────────┘

1.2 指针重新赋值导致的内存泄漏

// 文件:memory_leak_demo.cpp#include <iostream>#include <cstdlib>using namespace std;
void pointer_reassign_leak() {    // 错误示例:指针重新赋值导致内存泄漏    char *p = (char *)malloc(10);    char *p1 = (char *)malloc(10);
    // 假设 p 指向地址 0x1000,p1 指向地址 0x2000    p = p1;  // ❌ p 原来指向的 0x1000 丢失了!内存泄漏!
    // 正确做法:先释放再赋值    // free(p);    // p = p1;}
/*┌─────────┐    分配前         分配后          重新赋值后│         │                  ┌─────┐         ┌─────┐│   p     │ ─────────► 0x1000│ 10B │    p ──►│ 10B │ (0x2000)│         │                  └─────┘         └─────┘│   p1    │ ─────────► 0x2000┌─────┐    p1 ──►┌─────┐│         │                  │ 10B │         │ 10B │ (0x2000)└─────────┘                  └─────┘         └─────┘                                              ┌─────┐                                         遗留 │ 10B │ ← 无法访问!                                              └─────┘*/

1.3 常见内存泄漏场景

// 文件:common_leaks.cpp#include <iostream>#include <cstring>using 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.cpp#include <iostream>#include <cstring>using 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;    }
    // 拷贝构造:引用计数+1    RefCountRAII(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;  // 拷贝构造,计数+1    cout << "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.cpp#include <iostream>#include <cassert>using 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析构,引用计数减1
    cout << "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.cpp#include <iostream>#include <memory>#include <vector>using namespace std;
// ==================== shared_ptr 使用 ====================void shared_ptr_demo() {    cout << "n===== shared_ptr 演示 =====" << endl;
    // 创建 shared_ptr    shared_ptr<int> sp1 = make_shared<int>(42);    shared_ptr<int> sp2(sp1);      // 拷贝构造,引用计数+1    shared_ptr<int> sp3 = sp1;     // 赋值,引用计数+1
    cout << "sp1 use_count: " << sp1.use_count() << endl;  // 3    cout << "sp2 use_count: " << sp2.use_count() << endl;  // 3    cout << "*sp1 = " << *sp1 << endl;
    sp2.reset();  // 释放sp2,引用计数-1    cout << "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;  // 1    cout << "wp expired: " << wp.expired() << endl;      // false
    // 使用 weak_ptr 前需要 lock() 转换为 shared_ptr    if (auto locked = wp.lock()) {        cout << "通过 weak_ptr 访问: " << *locked << endl;    }
    sp.reset();  // 释放共享指针
    cout << "after reset, wp expired: " << wp.expired() << endl;  // true    if (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 持有 Child    child->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;  // 1    cout << "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.cpp#include <iostream>#include <memory>using 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_ptr    unique_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++ 智能指针 核心坑点 + 注意事项(浓缩可直接运行参考)#include <iostream>#include <memory>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); // ✅ 必须 move    if (!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.cpp#include <iostream>#include <cstdlib>using 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/),并保留原文链接。