• 【C++】内存管理到用new申请堆内存


    目录

    前言

    一、C/C++中程序内存区域划分

    二、C++使用new申请堆内存

    1.new和delete的使用

    2.new和delete的底层实现


    前言

            hello~❥(ゝω・✿ฺ) 大家好呀!欢迎能够看我的这一篇关于C++的学习笔记,让我们一起进步吧~

            我们首先要了解到的是C/C++下的内存管理,然后由C语言用malloc在堆上申请内存过渡到C++语言用new在堆上申请内存观察这两者的区别和差异,以及探究new的底层实现原理!

    一、C/C++中程序内存区域划分

            我们知道,一份源文件经过预处理、编译、汇编、链接步骤之后,就会变成一份二进制文件。此时其是存储在磁盘上的。当我们点击运行时,其属性加载进OS的PCB,排入cpu的运行队列成为一个进程。而此时此程序相关的二进制代码和数据才会进内存,但是此内存是虚拟内存,通常大小只有4g。

            此时细化给程序分配的内存就如下图所示:

             比如,区分如下变量内存空间,判断在那个区间:

    1. int a = 1;
    2. static int b = 2;
    3. int main()
    4. {
    5. int c = 3;
    6. int* d = (int*)malloc(sizeof(int));
    7. if (d == NULL)
    8. {
    9. perror("malloc");
    10. return 0;
    11. }
    12. *d = 4;
    13. char e[] = "abcd";
    14. const char* f = "abcd";
    15. cout << a << b << c << *d << e << f << endl;
    16. free(d);
    17. d = nullptr;
    18. return 0;
    19. }

            a是全局变量,所以在数据段(全局数据);b是静态全局变量,所以在数据段(静态数据);c是局部变量,在栈帧中开辟,所以在栈上;d是地址,由栈指向堆内某块内存,所以d在栈上;*d是指针解引用,指向堆内存,所以在堆上;e同样是一个地址,所以在栈上;*e此时指向栈内存,因为在之前编译器做的就是将“abcd”从常量区拷贝到栈上给e数组;f是地址,所以在栈上;*f此时指向常量,常量区位于代码段(只读常量)。

            了解了内存分配后,我们就可以知道平时c语言使用malloc函数的时候就是向堆内存申请空间。但是C++增加了自定义类型之后,我们想在堆上给自定义类型开空间怎么办呢?同时自定义类型设计构造函数以及初始化的问题,这些该如何解决呢?C++就推出了new操作符。

    二、C++使用new申请堆内存

    1.new和delete的使用

            如图C语言中的malloc和free配对一样,C++中新增了new和delete进行配对。同样的是在堆上开辟空间,另一个就是在堆上释放空间。

    new:

    数据类型 变量名 = new 数据类型;

            在new数据类型后加(n)表示给此堆变量赋初始值

            在new数据类型后加[n]表示是数组

        数组类型赋给初始值:(注意C++11支持)

                = new 数据类型[n]{...}    ...中内置类型是0~n个数据,自定义类型必须满(一个类型需要多次赋值就在里面加{})

    delete:

    delete 变量名;

            如果是数组

    delete[] 变量名;

    注意:

    new  对应 delete  new [] 对应 delete[]   必须一一对应,不对应的话会出问题。

    对于内置类型,delete可以等价于free

            比如如下内置类型使用new申请堆空间:

    1. int main()
    2. {
    3. int* a = new int;
    4. int* b = new int(1);
    5. int* c = new int[3];
    6. int* d = new int[3]{ 1, 2, 3 };
    7. cout << *a << endl;
    8. cout << *b << endl;
    9. for (int i = 0; i < 3; i++)
    10. {
    11. cout << c[i] << " ";
    12. }
    13. cout << endl;
    14. for (int i = 0; i < 3; i++)
    15. {
    16. cout << d[i] << " ";
    17. }
    18. cout << endl;
    19. delete a;
    20. delete b;
    21. delete[] c;
    22. delete[] d;
    23. return 0;
    24. }

            

    可以通过上面的代码发现,new和malloc等的区别就是不用检查是否传回来是空和不用强转类型,那么new是如何检查能否申请成功的呢?利用的是异常处理。--异常处理  

            比如可以向虚拟内存申请大概2g的空间,那么就会异常处理(x32位平台下):

    1. int main()
    2. {
    3. try
    4. {
    5. char* a = new char[1024u * 1024u * 1024u * 2 - 1];
    6. delete[] a;
    7. }
    8. catch (const exception& e)
    9. {
    10. cout << e.what() << endl;
    11. }
    12. return 0;
    13. }

            其中try和catch就是接收抛出的异常,如果存在异常就进入下面的catch语句中,what函数就输出错误:

            

            -- 分配不当 -- 

            那么初看对于内置类型,new与delete和malloc与free也没有多大的区别,最多就是malloc如果申请不到堆内存就返回NULL,而new的处理就是抛出异常。但是针对于自定义类型,这个差异就会非常大了:

    1. class Test
    2. {
    3. int _num;
    4. public:
    5. Test(int num = 0)
    6. :_num(num)
    7. {
    8. cout << "Test()构造" << endl;
    9. }
    10. ~Test()
    11. {
    12. cout << "Test()析构" << endl;
    13. }
    14. };
    15. int main()
    16. {
    17. Test* t = new Test;
    18. Test* t2 = new Test[3]{ 1, 2, 3 };
    19. delete t;
    20. delete[] t2;
    21. return 0;
    22. }

             可以发现,new不仅仅在堆内申请空间,并且也调用了自定义类型的构造函数。(赋的有初始值就调用对应的构造函数,否则调用默认构造函数),然后delete会在释放堆内存之前,先调用析构函数,然后才会释放空间。

    2.new和delete的底层实现

            new和delete终究是两个操作符,而malloc是一个函数。那么new和delete是如何完成申请空间的同时调用构造函数和析构函数呢?

            首先,就不得不说两个重要的系统实现的全局函数:operator new 和 operator delete:

    1. /*
    2. operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
    3. 尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
    4. */
    5. void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
    6. {
    7. // try to allocate size bytes
    8. void *p;
    9. while ((p = malloc(size)) == 0)
    10. if (_callnewh(size) == 0)
    11. {
    12. // report no memory
    13. // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
    14. static const std::bad_alloc nomem;
    15. _RAISE(nomem);
    16. }
    17. return (p);
    18. }
    1. /*
    2. operator delete: 该函数最终是通过free来释放空间的
    3. */
    4. void operator delete(void *pUserData)
    5. {
    6. _CrtMemBlockHeader * pHead;
    7. RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    8. if (pUserData == NULL)
    9. return;
    10. _mlock(_HEAP_LOCK); /* block other threads */
    11. __TRY
    12. /* get a pointer to memory block header */
    13. pHead = pHdr(pUserData);
    14. /* verify block type */
    15. _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    16. _free_dbg( pUserData, pHead->nBlockUse );
    17. __FINALLY
    18. _munlock(_HEAP_LOCK); /* release other threads */
    19. __END_TRY_FINALLY
    20. return;
    21. }

            上面是两个函数的底层实现,可以大致的看出来实际上operator new用malloc申请堆内存,然后做了一些异常处理,同理,operator delete也是用free进行释放空间的。

            然后我们可以查看其汇编代码:

            就可以发现,new在调用operator new函数后即申请空间后就会去调用构造函数。同理,delete也是先调用的~析构,之后在释放空间。

            综上:

    new:

            调用operator new,里面malloc存在的意义:1.帮助new开空间,封装malloc,为了符合C++new的失败机制 -- 失败抛异常之后。之后调用构造函数。

    delete:

            调用析构函数清理,然后调用operator delete,里面封装了free,释放空间。

    未完待续.......

  • 相关阅读:
    扬帆牧哲—shopee应该具备的运营思维
    单目标分割标签图叠加代码
    java 实现命令行模式
    基于 MinIO 对象存储保障 Rancher 数据
    Pytorch--3.使用CNN和LSTM对数据进行预测
    FastDFS在centos7上的配置
    云耀服务器L实例搭配负载均衡部署Linux 可视化宝塔面板
    征稿丨IJCAI‘23大模型论坛,优秀投稿推荐AI Open和JCST发表
    除铜树脂-KF340
    git_note
  • 原文地址:https://blog.csdn.net/weixin_61508423/article/details/126213835