欢迎光临
我们一直在努力

深入理解C++系列(05)——C++的内存管理

文章目录

    • 上期回顾
  • C/C++ 内存管理
    • 一、本讲核心主线
    • 二、C/C++ 内存分布
    • 三、典型变量位置判断
    • 四、回顾一下C 语言动态内存管理
      • 1. `malloc`
      • 2. `calloc`
      • 3. `realloc`
      • 4. `free`
    • 五、C++ 动态内存管理:`new/delete`⭐️⭐️⭐️
      • 1. 操作内置类型
      • 2. 操作自定义类型
      • **知识点总结:**
    • 六、`operator new` 和 `operator delete`
    • 七、`new/delete` 实现原理
      • 1. 内置类型
      • 2. 自定义类型的 `new` 原理
      • 3. 自定义类型的 `delete` 原理
      • 4. `new T[N]` 原理
      • 5. `delete[]` 原理
    • 八、定位 new:`placement new`⭐️⭐️
    • 九、`malloc/free` 和 `new/delete` 的区别
    • 十、易错点汇总
    • 下期预告
    • 模版初阶
    • 结语

◆ 博主: @此生决int

分享编程知识!深挖底层原理!持续原创更新!

热门专栏: 深入理解 C++系列|算法系列 快速复习系列 |Java 速通系列

📌 本系列知识点前后关联较强,建议按照专栏顺序阅读哦

本系列主要面向的阅读人群

1,学完 C 语言,准备系统学习 C++ 的同学

2,已经学习过 C++,希望重新梳理知识体系的同学

3,想深入理解 C++ 设计思想与底层原理,而不仅仅停留在语法层面的同学

不止教你会写代码,更带你理解代码为什么这样设计!

—

上期回顾

hello,大家好,这里是此生决int,上一期我们终于结束了面向对象的语音C++的一个重要组成部分——类和对象,今天我们就一起来看看变量在C++里面是怎么存储的吧,看看他相较于C语言有哪些改进之处!

C/C++ 内存管理


一、本讲核心主线

这一讲主要解决三个问题:

  • C/C++ 程序中的变量分别存在哪里?
  • C 和 C++ 分别如何动态申请和释放堆空间?
  • malloc/free 和 new/delete 到底有什么区别?
  • 一句话总结:

    malloc/free 只负责堆空间申请和释放;new/delete 除了申请和释放空间,还会对自定义类型自动调用构造函数和析构函数。


    二、C/C++ 内存分布

    常见内存区域:

    区域存放内容
    栈 非静态局部变量、函数参数、返回值等
    堆 程序运行时动态申请的空间
    数据段 / 静态区 全局变量、静态变量
    代码段 / 常量区 可执行代码、只读常量、字符串常量
    内存映射段 动态库映射、共享内存等,了解即可

    三、典型变量位置判断

    int globalVar = 1;
    static int staticGlobalVar = 1;

    void Test()
    {
    static int staticVar = 1;
    int localVar = 1;
    int num1[10] = { 1, 2, 3, 4 };
    char char2[] = "abcd";
    const char* pChar3 = "abcd";
    int* ptr1 = (int*)malloc(sizeof(int) * 4);
    }

    重点结论:

    名称所在区域原因
    globalVar 数据段 / 静态区 全局变量
    staticGlobalVar 数据段 / 静态区 静态全局变量
    staticVar 数据段 / 静态区 静态局部变量
    localVar 栈 普通局部变量
    num1 栈 局部数组整体在栈上
    char2 栈 局部字符数组整体在栈上
    *char2 栈 数组元素在数组内部
    pChar3 栈 局部指针变量本身在栈上
    *pChar3 代码段 / 常量区 指向字符串常量
    ptr1 栈 局部指针变量本身在栈上
    *ptr1 堆 malloc 申请的空间

    最容易错的是:(后面学string类如果不懂那就是没有理解这里!!!)

    char char2[] = "abcd"; // 字符数组,内容拷贝到栈上
    const char* pChar3 = "abcd"; // 指针在栈上,字符串常量在常量区

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


    四、回顾一下C 语言动态内存管理

    C 语言中常用:

    malloc
    calloc
    realloc
    free

    1. malloc

    int* p = (int*)malloc(sizeof(int) * 4);

    特点:

    • 在堆上申请指定字节数的空间。
    • 不初始化申请到的空间。
    • 返回 void*,C++ 中需要强转。
    • 申请失败返回 NULL。

    注释1 一般都要强转,但C语言存在隐式类型转换,C++不支持,所以必须这样写

    int* p = (int*)malloc(sizeof(int) * 4);


    2. calloc

    int* p = (int*)calloc(4, sizeof(int));

    特点:

    • 申请 4 个 int 大小的空间。
    • 会把空间初始化为 0。

    3. realloc

    int* p2 = (int*)realloc(p1, sizeof(int) * 10);//注释1

    特点:

    • 调整原动态内存块大小。
    • 可能原地扩容,也可能重新开空间并拷贝旧数据。
    • 返回新地址。

    注意:

    int* p3 = (int*)realloc(p2, sizeof(int) * 10);
    free(p3);

    这里不需要再 free(p2),因为 realloc 成功后应以返回的新指针为准。 注释1 这里可以写成这样吗?

    p1 = (int*)realloc(p1, sizeof(int) * 10);//注释1

    不行! realloc 如果失败,会返回 NULL,但不会释放原来的内存。如果直接使用:

    p = realloc(p, size);

    会导致原指针被 NULL 覆盖,从而丢失原内存地址,最终造成内存泄漏。


    4. free

    free(p);

    特点:

    • 释放堆空间。
    • 只释放空间,不会调用析构函数。注释1

    注释1 例如,free(类类型);他只会释放对应的空间,而不会调用类类型的析构函数。总之,只需要记住:malloc/free、new/delete、new[]/delete[] 一一对应,不能混用。就没有什么问题!(具体的后面标题:2,操作自定义类型会讲到)


    五、C++ 动态内存管理:new/delete⭐️⭐️⭐️

    C++ 中推荐使用:

    new
    delete
    new[]
    delete[]

    1. 操作内置类型

    int* p1 = new int;//int*p1,定义一个int指针,new int堆上申请一个int大小的空间,但是没有初始化!!!
    int* p2 = new int(10);
    int* p3 = new int[3];

    delete p1;
    delete p2;
    delete[] p3;

    注意匹配:一定要匹配使用。

    申请释放
    new delete
    new[] delete[]

    2. 操作自定义类型

    class A
    {
    public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
    };

    A* p = new A;
    delete p;

    核心区别:

    • new A:申请空间 + 调用构造函数。
    • delete p:调用析构函数 + 释放空间。

    而:

    A* p = (A*)malloc(sizeof(A));
    free(p);

    只会申请和释放空间,不会调用构造和析构。 我的理解: A* p = new A; 既申请了空间,又创建了对象,调用了构造函数, delete p; 他既调用了析构函数,又释放了空间, A* p = (A*)malloc(sizeof(A)); 和free§; 只是申请了空间,没有创建对象,也没有调用构造和析构函数!

    知识点总结:

    free 只负责释放 malloc 申请的那块原始内存,不会调用对象的析构函数,因此对象内部管理的资源不会被自动释放。 new/delete 不仅负责内存申请和释放,还负责构造函数和析构函数的调用,所以 C++ 中管理类对象通常使用 new/delete。 同时需要保证 malloc/free、new/delete、new[]/delete[] 一一对应,不能混用。


    六、operator new 和 operator delete

    重点区分:

    名称本质
    new 操作符,用户使用
    delete 操作符,用户使用
    operator new 全局函数,底层负责申请空间
    operator delete 全局函数,底层负责释放空间

    new 的底层会调用 operator new。 delete 的底层会调用 operator delete。

    大致关系:

    new -> operator new -> malloc

    delete -> operator delete -> free

    operator new 申请失败时通常会抛出 bad_alloc 异常,而不是像 malloc 那样返回 NULL。


    七、new/delete 实现原理

    1. 内置类型

    对于内置类型,差别相对较小。

    int* p1 = (int*)malloc(sizeof(int));
    int* p2 = new int;

    主要区别:

    • malloc 失败返回 NULL。
    • new 失败抛异常。
    • new 写法更简单,不用手动计算字节数和强转。

    2. 自定义类型的 new 原理

    A* p = new A(10);

    步骤:

  • 调用 operator new 申请空间。
  • 在申请到的空间上调用构造函数,完成对象构造。

  • 3. 自定义类型的 delete 原理

    delete p;

    步骤:

  • 调用析构函数,清理对象内部资源。
  • 调用 operator delete 释放对象空间。

  • 4. new T[N] 原理

    A* p = new A[10];

    步骤:

  • 调用 operator new[] 申请连续空间。
  • 实际底层会调用 operator new。
  • 对 N 个对象分别调用构造函数。

  • 5. delete[] 原理

    delete[] p;

    步骤:

  • 对 N 个对象分别调用析构函数。
  • 调用 operator delete[] 释放空间。
  • 实际底层会调用 operator delete。

  • 八、定位 new:placement new⭐️⭐️

    定位 new 的作用:

    在已经分配好的原始内存空间上调用构造函数初始化对象。

    格式:

    new(place_address) Type;
    new(place_address) Type(initializer–list);//Type(初始化列表);

    示例:

    A* p = (A*)malloc(sizeof(A));//申请空间
    new(p) A(10);//指定空间创建对象,并调用构造函数
    p->~A();//指定空间调用析构函数
    free(p);//释放空间

    解释:

  • malloc 只申请一段原始空间,还不是对象。
  • new(p) A(10) 在这段空间上调用构造函数。
  • p->~A() 显式调用析构函数。
  • free(p) 释放空间。
  • 使用场景(了解)

    常和内存池配合使用。内存池只提供原始内存,自定义类型对象还需要显式构造。


    九、malloc/free 和 new/delete 的区别

    共同点:

    都是从堆上申请空间,都需要用户手动释放。

    区别:

    对比项malloc/freenew/delete
    本质 函数 操作符
    空间大小 手动计算字节数 根据类型自动计算
    返回类型 void*,C++ 中需强转 返回对应类型指针
    初始化 malloc 不初始化,calloc 清零 new 可以初始化
    失败处理 返回 NULL 抛异常
    自定义类型 不调用构造 / 析构 自动调用构造 / 析构
    数组申请 手动算总大小 new[] 指定对象个数
    释放方式 free delete / delete[]

    最重要区别:

    对自定义类型,new/delete 会调用构造函数和析构函数,malloc/free 不会。


    十、易错点汇总

  • char arr[] = "abcd" 的内容在栈上,const char* p = "abcd" 指向常量区。
  • 指针变量本身在哪里,看它怎么定义;它指向哪里,看它保存的地址来自哪里。
  • malloc 申请失败返回 NULL,new 失败抛异常。
  • new / delete 要配对,new[] / delete[] 要配对。
  • malloc/free 不会调用构造和析构。
  • operator new 不是 new 操作符本身,而是底层申请空间的函数。
  • 定位 new 只调用构造函数,不负责申请空间。
  • 用定位 new 构造的对象,需要手动调用析构函数。

  • 下期预告

    模版初阶

    结语

      本文到此结束,感谢大家的阅读!如果觉得本文对你有所帮助,欢迎点赞、收藏、关注,也欢迎在评论区一起交流讨论。   也欢迎订阅我的 深入理解 C++系列:从语法入门到底层原理,系统掌握现代 C++ 算法系列:从入门到精通,蓝桥杯、ACM、LeetCode 与面试算法全路线 快速复习系列:知识梳理、查漏补缺,考前冲刺必备 Java 速通系列:已学 C 语言,快速上手 Java,轻松备战期末考试


      愿每一次敲下键盘,都比昨天更进一步!

      愿每一行代码落下,都让未来多一种可能!

    赞(0)
    未经允许不得转载:171主机测评 » 深入理解C++系列(05)——C++的内存管理
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址