【C++入门】C/C++内存管理 --建议收藏

分手后的思念是犯贱 2022-09-16 14:17 259阅读 0赞

C/C++内存管理

  • 前言
  • 一、内置类型
  • 二、自定义类型
  • 三、operator new/operator delete
  • 四、new和delete的总结
    • 小测试,画出下面的st与pst所在栈帧位置
  • 五、定位new表达式
  • 六、malloc/free和new/delete的区别
  • 总结

前言

本篇主要比较从malloc,free的组合到new,delete的变化,本章会简述其过程为什么是这样的!!


一、内置类型

  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. //C当中开辟十个整形空间
  6. int* pi = (int*)malloc(sizeof(int) * 10);
  7. free(pi);
  8. pi = NULL;
  9. //C++当中开辟十个整形空间
  10. int* pi2 = new int[10];
  11. delete pi2;
  12. pi2 = nullptr;
  13. return 0;
  14. }

在这里插入图片描述

可以发现对于内置类型来说,我们现在能观察到的就是new相对于malloc可以少写一个返回值,并且new是一个操作符。但是大体上我们观察malloc和new是差不多的,看完后面再总结这点。


二、自定义类型

  • 以栈为例,展开new对于自定义类型不同于malloc的不同之处。

    class Stack
    {
    public:

    1. Stack(int x = 10)
    2. :a(new int[x])
    3. ,_capacity(x)
    4. ,_size(0)
    5. {
    6. cout << "Stack(int x = 10)" << endl;
    7. }

    private:

    1. int _size;
    2. int _capacity;
    3. int* a;

    };
    int main()
    {

    1. //C
    2. Stack* pst = (Stack*)malloc(sizeof(Stack));
    3. free(pst);
    4. pst = NULL;
    5. //C++
    6. Stack* pst2 = new Stack;
    7. delete(pst2);
    8. pst2 = nullptr;
    9. return 0;

    }

在这里插入图片描述

  • 结论1:

可以从调试当中看出,我们的new对于内置类型会调用对象的构造函数进行初始化,并且还要说一点,他比起malloc还会对异常进行处理,当我们尝试在32位的情况开辟2g的内存的时候,我们看看编译器会报什么错–抛异常
在这里插入图片描述

  • 抛异常后的选择

1.抛异常了,所以一般我们在new完之后要记得捕捉异常,因为有的时候有些地方申请内存失败,但是他罪不至死,通过捕捉异常我们可以进行再处理!
2.不做任何处理

在这里插入图片描述

  • 异常捕捉打印结果

bad allocation,就是我们捕捉异常打印的结果,从下面的代码也可以看见我们的“haha’”并没有打印,说明一出现异常的时候catch就把我们的异常捕获了。

  1. int main()
  2. {
  3. size_t i = 2;
  4. try
  5. {
  6. char* pi = new char[i * 1024 * 1024 * 1024];
  7. cout << "haha" << endl;
  8. }
  9. catch (const exception& e)
  10. {
  11. cout << e.what() << endl;
  12. }
  13. }

三、operator new/operator delete

这个函数不是重载函数,不是重载函数,不是重载函数!!!
new和delete是用户在动态申请内存和释放所用到的操作符,operator new和operator delete是系统提供的全局函数,全局函数,全局函数!!!

废话不多说,我们直接先来看看底层代码实现

  1. /* operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败, 尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。 */
  2. void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) {
  3. // try to allocate size bytes
  4. void *p;
  5. // ==0即申请失败
  6. while ((p = malloc(size)) == 0)
  7. if (_callnewh(size) == 0)
  8. {
  9. // report no memory
  10. // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
  11. static const std::bad_alloc nomem;//定义一个异常对象
  12. _RAISE(nomem);//对异常做处理
  13. }
  14. return (p);
  15. }

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

我们可以看出,operator new对比起malloc只是多了一个异常的处理,内部动态申请内存空间也是由malloc代劳,它的返回值和malloc甚至都是一模一样,所以调用operator new和malloc并太大区别,只是在抛异常这点做了处理,并且可以看出他没有调用构造函数初始化!
operator delete底层涉及到阻塞队列,线程等概念,在这里我们初步理解为operator delete只是对free进行了一个封装,用起来和free差不多即可。当中释放空间的函数是用free的。

总结:假设对A类来说,new作用符就会干两件事情,调用operator new,和调用A的构造函数进行初始化。
在这里插入图片描述


四、new和delete的总结

1.对于内置类型来说,new和malloc,free和delete基本类似,不同的地方在于,new/delete是申请/释放单个元素的空间,new[]和delete[]申请的是连续空间,而且new而在申请空间失败时会抛异常,malloc会返回NULL。

2.对于自定义类型来说,new的原理: 即调用operator new开空间并且在申请的空间上执行构造函数,完成对象的构造。
delete的原理: 在空间上调用析构函数,完成对象中资源的清理工作,再调用operatir delete函数释放对象的空间。
其中new T[n]的原理: 调用operator new[]函数,operator new[]当中又是通过调用operator
new函数完成N个对象空间的申请,然后对这些空间执行N次构造函数。delete[]的原理:释放的对象空间上执行了N次析构函数,完成N个对象中资源的清理,调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。

小测试,画出下面的st与pst所在栈帧位置

  1. class Stack
  2. {
  3. public:
  4. Stack(int cap)
  5. :_a(new int[cap])
  6. ,_top(0)
  7. ,_cap(cap)
  8. { }
  9. private:
  10. int* _a;
  11. int _top;
  12. int _cap;
  13. };
  14. int main()
  15. {
  16. Stack st(5);
  17. Stack* pst = new Stack(5);
  18. //....
  19. delete pst;
  20. pst = nullptr;
  21. return 0;
  22. }

需要注意这两个实例化出来的对象一个是在栈,一个在堆,他们的声明周期是不一样的,但是他们的_a都是在堆上面,也证实了调用delete堆上的对象时,会先把它的资源(这里的_a)给清理了。这样才不会造成内存泄漏。
在这里插入图片描述


五、定位new表达式

定位new的作用是在已分配的原始内存空间中调用构造函数初始化一个对象

  • 使用方式:两种方式差异即type有无默认构造函数!

new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

定位new一般和内存池使用,正因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示构造函数进行初始化。
但我们这里没涉及内存池,所以我们换一个测试,打印一句话证明调用成功即可。

  1. class Stack
  2. {
  3. public:
  4. Stack(int cap)
  5. :_a(new int[cap])
  6. ,_top(0)
  7. ,_cap(cap)
  8. {
  9. cout << "完成一次调用定位new\n";
  10. }
  11. ~Stack()
  12. {
  13. free(_a);
  14. _a = nullptr;
  15. cout << "完成一次释放空间\n";
  16. }
  17. private:
  18. int* _a;
  19. int _top;
  20. int _cap;
  21. };
  22. int main()
  23. {
  24. Stack* pst = (Stack*)malloc(sizeof(Stack));
  25. new(pst) Stack(5);//这里调用Stack的构造需要传参
  26. delete(pst);
  27. pst = nullptr;
  28. return 0;
  29. }

在这里插入图片描述


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

我们从三点出发:特点和用法底层原理区别处理错误的方式

  • malloc/free是函数,new/delete是操作符(特点)
  • malloc申请空间需要手动计算空间,new的话只需要空间的类型就可以(用法)
  • malloc的返回值是void,new的返回值就是类型的指针(用法)。
  • malloc失败返回NULL,delete失败抛异常(处理错误的方式)
  • 申请自定义类型对象时,malloc/free只会开辟空间,new会开辟好空间调用构造函数,delete会先调用析构函数再释放空间。new在申请空间用到了malloc,delete用到了free。(底层原理区别)

调用new的时候我们都做了什么?

我们调用new相当于调用operator new先把空间开出来(抛异常),new当中实现了定位new显示调用构造函数,然后还利用了模板把返回值处理了。
编辑器的话指令少了定位new直接调用,但我们调用的时候就要显式调用。


总结

C++内存管理结束啦!!下一章节将会进行模板的讲述,讲述完之后就是STL,相当于C++终于可以入门啦!!!

发表评论

表情:
评论列表 (有 0 条评论,259人围观)

还没有评论,来说两句吧...

相关阅读

    相关 C++】C++内存管理

    写一个好的C++程序,我们要懂得好多东西,比如说最基本的面向对象编程思想,C++的封装、继承、多态机制,设计模式等,还有一个很重要的内容便是性能优化,像C/C++这种接近底层的

    相关 C/C++内存管理

    内存管理 在C和C++中,我们最关心的就是内存,一旦与内存沾上边,所以也就导致相对其它语言而言,它学起来就没有那么容易。 内存布局 ![在这里插入图片描述][wat