• C++ 基础与深度分析 Chapter8 动态内存管理(动态内存基础、智能指针、相关问题)



    在这里插入图片描述

    动态内存基础

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    栈的对象在内存中开辟的空间都比较紧密,连续创建的对象x和y,他们在栈空间是非常紧密的。函数调用,栈针就会出栈。函数结束,对象就自动销毁了。

    堆是在运行期动态扩张。因为有的时候,需要多少内存,是在运行期才能确定的。所以就需要堆。比如刚开始分配了10个int的内存,使着过程中,发现不够用了,再分配20个int。这就是动态的扩展内存,动态缩小内存也同理。

    对比栈的内存,栈针弹出会自动销毁。但是堆不会,需要通过delete进行释放。

    如果控制堆内存呢?我们通过new和delete来构造、销毁对象。

    栈内存:

    int main()
    {
        int x; // 系统在栈中开辟一块内存给x
        x = 2; // 在x这个内存位置把2写入
        cout << x << endl; // 系统会把x对应内存的数值取出来,传递给cout
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    堆内存:

    int main()
    {
        int* y = new int(2); // 由new分配了一块堆内存,int有多大,就分配多大
        // new返回的是一个内存的地址,我们需要用一个指向该堆内存的指针来接收
        cout << *y << endl; // 对地址解引用,才能获得堆内存里的值
        //...
        delete y; // 告诉系统y对应的内存我,我们不需要了
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    int* fun()
    {
        int* res = new int(2);
        return res; // 返回堆内存res对应的地址
    }
    
    int main()
    {
        int* y = fun(); // 因为我们fun函数开辟的是堆内存,返回是堆内存的地址
        // 所以函数结束,堆中对象还在,y可以取到
        cout << *y << endl;
        //...
        delete y; // 告诉系统y对应的内存我,我们不需要了
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    int* fun()
    {
        int res = 2;
        return &res; //  warning: address of stack memory associated with
        // local variable 'res' returned [-Wreturn-stack-address]
    }
    
    int main()
    {
        int* y = fun(); 
        cout << *y << endl;
        //...
        delete y; 
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    对象的构造:1分配内存;2在内存上构造对象;
    对象的销毁:1对象销毁;2把之前分配的内存归还给系统;

    在这里插入图片描述

    int main()
    {
        int* y = new int[5]{1, 11, 22, 33, 44};
        cout << y[1] << endl; // 11
        delete[] y; // 构造的数组,要加[]来删除
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    内存片段:有时候我们分配了一大块连续的内存,但是释放的时候,可能只是释放了其中一部分零散的内存,所以不连续。

    通过nothrow new来判断分配内存是否成功。如果失败了,系统不抛出异常,而是返回一个nullptr的指针。

    #include <iostream>
    #include <new>
    using namespace std;
    
    int main()
    {
        int* y = new(std::nothrow) int[5]{1, 11, 22, 33, 44};
        if (y == nullptr)
        {
            //...
        }
        else
        {
            cout << y[1] << endl; 
            delete[] y; 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    palecement new:不需要分配内存了,只需要在已分配的内存上构造对象。vector就是用这个原理。vector动态增长。

    vector增加一个元素的原理不是:若来了一个新的元素,先开辟一块新的内存,把原有的值拷贝过来,再把新的元素放进来。vector一般不这么做,而是多分配一些。这样不用一直拷贝。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    int main()
    {
        int* y = new auto(3); //  new auto 系统自动推导
    }
    
    • 1
    • 2
    • 3
    • 4

    new与对象对齐
    在这里插入图片描述
    delete

    int main()
    {
        int* ptr = new int;
        delete ptr;
        int* ptr2 = new int[5];
        delete[] ptr2; // 销毁数组
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    placement delete:只关注内存上构造的对象销毁掉,但是不会把内存归还给系统。还是vector的删除一个元素的原理,只会把最后一个元素销毁掉,但是对象对应的内存并不归还给系统。

    对于内建的类型,不需要考虑placement delete。只有我们定义了类的析构函数。析构函数会刷新缓存,关掉文件。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    int main()
    {
        int* ptr = new int(3);
        cout << ptr << endl; // 0x7fbec9c05a30
        delete ptr;
        ptr = nullptr;
        cout << ptr << endl; // 0x0
        delete ptr; // ptr=nullptr系统什么都不做
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    智能指针

    在这里插入图片描述
    new和delete是关系很紧密的两条语句,但是什么时候调用delete我们说不清,容易产生不销毁或者多销毁,而且还有内存所有权的问题。所以引入了智能指针的概念。

    Shared_ptr——基于引用计数的共享内存解决方案

    int main()
    {
        // int* x = new int(3);  之前的写法
        // int* x(new int(3));
        //int* 等价于 std::shared_ptr<int>
        std::shared_ptr<int> x(new int(3)); //x是shared_ptr构造的对象,new int(3)初始化x
        cout << *x << endl; // 3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    share_ptr是一个智能指针,它的机制是引用计数,通过引用计数来维护内存什么时候进行销毁。我们不用担心内存泄漏,整个x在main结束,x的生命周期结束,x是一个share_ptr包含一个析构函数,x结束,析构函数销毁new int构造的内存。

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    int main()
    {
        std::shared_ptr<int> x(new int(3));  // x包含两个信息,一个是3,另一个是引用计数:1
        // 引用计数,同一时刻,有多少对象引用它
        std::shared_ptr<int> y = x; // 同样不担心程序执行完new int没有被销毁,这时引用计数是:2
        // y和x共享一个引用计数对象,c++规定先构造的后销毁,后构造的先销毁
        // y先销毁(调用shared_ptr<int>析构函数),new int(3)的引用由2变成1
        // 再销毁x(调用shared_ptr<int>析构函数)
        // 由于引用计数的存在,保证内存被销毁,且不会销毁多次
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    get()

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    std::shared_ptr<int> fun()
    {
        std::shared_ptr<int> res(new int(3));
        return res;
    }
    
    int main()
    {
        auto y = fun();
        cout << *y << endl; // 智能指针本质是指针,对其解引用,返回3
        cout << *(y.get()) << endl; // 3, y.get()返回int*
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    std::shared_ptr<int> fun()
    {
        std::shared_ptr<int> res(new int(3));
        return res;
    }
    
    void fun2(int* x)
    {
    
    }
    
    int main()
    {
        auto y = fun();
        cout << *y << endl; // 智能指针本质是指针,对其解引用,返回3
        cout << *(y.get()) << endl; // 3, y.get()返回int*
        // fun2(y); // 报错 note: candidate function not viable: no known conversion from 'std::shared_ptr<int>' to 'int *' for 1st argument
        fun2(y.get()); // 确保实参是int*
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    reset()

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    std::shared_ptr<int> fun()
    {
        std::shared_ptr<int> res(new int(3));
        return res;
    }
    
    int main()
    {
        auto y = fun();
        y.reset(new int(100));
        cout << *y << endl; // 100, reset尝试释放y包含的指针的资源
        // 然后接受一个新的指针,引用计数+1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    指定内存回收逻辑:把内存放在一个池子里,一般从内存池里拿,不用的的时候放回池子里,不涉及new和delete操作,可以提升速度。
    在这里插入图片描述
    make_shared

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    int main()
    {
        std::shared_ptr<int> ptr(new int(3));
        // 等价于下面的
        std::shared_ptr<int> ptr2 = std::make_shared<int>(3);
        auto ptr2 = std::make_shared<int>(3); // 简化代码
        // make_shared 确保局部性,分配一块内存,把3写入,系统还要构造引用计数
        // make_shared 进行了优化,把2块内存放的尽量近
        // 好处是读取引用计数和int时,只访存1次,提高效率
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    支持数组
    在这里插入图片描述

    int main()
    {
        std::shared_ptr<int[]> ptr(new int[5]); // int[] 告诉系统用delete[] 进行删除
    }
    
    • 1
    • 2
    • 3
    • 4

    c++20可以用make_shared
    在这里插入图片描述
    注意: shared_ptr 管理的对象不要调用 delete 销毁
    在这里插入图片描述
    在这里插入图片描述

    unique_ptr——独占内存的解决方案

    在这里插入图片描述
    一般意义指针就是共享的行为,但是不排除独占的时候。就要用unique_ptr

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    int main()
    {
        std::unique_ptr<int> x(new int(3)); // x是独占int(3)这块内存资源的
        // std::unique_ptr<int> y(x); // 报错,y不能共享x的内存
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    int main()
    {
        std::unique_ptr<int> x(new int(3)); 
        cout << x.get() << endl; // 0x7fbeeb405a30
        std::unique_ptr<int> y = std::move(x); // x将亡值,x的资源给y
        cout << x.get() << endl; // 0,y拿走了x的资源
        cout << y.get() << endl; // 0x7fbeeb405a30
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    weak_ptr——防止循环引用而引入的智能指针

    在这里插入图片描述
    shared_ptr通过引用计数来销毁。但是17-20行
    在这里插入图片描述
    所以要解决循环引用,使用weak_ptr来解决。
    weak_ptr可以和shared_ptr共享一块内存,但是weak_ptr并不会增加引用计数。这样就解决了循环引用的问题。

    动态内存的相关问题

    在这里插入图片描述

    sizeof不会返回动态内存的大小:

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    int main()
    {
        int* ptr = new int(3);
        cout << sizeof(ptr) << endl; // 8 ptr是一个int*,8个字节
        int* ptr2 = new int[3];
        cout << sizeof(ptr2) << endl; // 8 ptr是一个int[]的起始地址,8个字节
        // new返回的是内存的地址,sizeof返回的一定是int*的大小
        // sizeof并不会返回int*指向内存对象的尺寸
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用分配器( allocator )来分配内存

    对象的构造分2步:1分配内存;2构造对象
    allocator成员函数:
    在这里插入图片描述
    allocate分配内存

    int main()
    {
        std::allocator<int> al; // 分配器al,用来分配int类型的对象
        int* ptr = al.allocate(3); // 分配了一块内存,这个内存可以包含3个int,然后返回
        // 注意这里不是构造了3个int的内存。
        // 差异在于allocate不包含int的初始化,只是内存分配,不初始化。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    deallocate回收内存

    int main()
    {
        std::allocator<int> al; // 分配器al,用来分配int类型的对象
        int* ptr = al.allocate(3); // 分配了一块内存,这个内存可以包含3个int,然后返回
        al.deallocate(ptr, 3); // 释放内存
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用 malloc / free 来管理内存

    malloc / free另外一对内存管理的方法。对比new和delete。既然有了new和delete,为什么还要这个呢?malloc / free 继承自c语言。new和delete都包含2个操作,分配和对象。但是malloc和free只会分配内存,不会在内存上构造对象,因为c语言内部,不是很强调对象的构造。c++引入类之后,才强调对象的概念。
    在这里插入图片描述
    但是mallo不保证内存的对其,所以引入了aligned_alloc来分配内存对齐

    使用 aligned_alloc 来分配对齐内存

    如果有可能还是使用allocator。

    动态内存与异常安全

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    int main()
    {
        int* ptr = new int(3);
        //...这里可能会有异常,
        // 异常:程序不应该出现的状态
        // 系统会跳到异常捕获,去处理异常
        // 如果系统发生异常,这里就不会执行13行了,内存就没有释放
        delete ptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如果内存没有及时释放,内存在堆上不断的激增,最后导致系统崩溃。

    #include <iostream>
    #include <new>
    #include <memory>
    using namespace std;
    
    void fun()
    {
        std::shared_ptr<int> ptr(new int(3));
        //...
        // 
    }
    
    int main()
    {
        //...
        // 因为用了shared_ptr智能指针,不论调用多少次fun,内存都可以被释放,提升安全性
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    C++ 对于垃圾回收的支持

    垃圾回收:动态指定了内存,现在不用了,需要回收掉。python,java都支持垃圾回收。系统会判断内存没有使用,然后自动回收。c++对于垃圾回收可以说是欲拒还迎的状态。
    在这里插入图片描述

  • 相关阅读:
    生成带分表和水印的excel压缩文件
    MySQL碎片整理小节--实例演示
    智能座舱的「交互设计」大战
    校园二手服务平台
    考研计算机网络(第一章 概述)
    Vue开发历程---音乐播放器的继续
    SpringBoot图片文件上传
    基于51单片机的时钟闹钟温度计LCD1602显示proteus仿真原理图PCB
    ​LeetCode解法汇总2864. 最大二进制奇数
    SM2密码算法数据结构
  • 原文地址:https://blog.csdn.net/weixin_43716712/article/details/125406261