• 优化C++资源利用:探索高效内存管理技巧


    W...Y的主页 😊

    代码仓库分享💕 


    🍔前言:
    我们之前在C语言中学习过动态内存开辟,使用malloc、calloc与realloc进行开辟,使用free进行堆上内存的释放。进入C++后对于动态内存开辟我们又有了新的内容new与delete。今天我们来学习C++中的动态内存开辟!

    我们先来进行一下内存管理的复习。

    目录

    C/C++内存分布

    C语言中动态内存管理方式:malloc/calloc/realloc/free 

    C++内存管理方式

    new/delete操作内置类型

    new和delete操作自定义类型

    operator new与operator delete函数

    new和delete的实现原理

    内置类型

     自定义类型

    定位new表达式(placement-new)

    C++与new的使用场景 


    C/C++内存分布

    1. int globalVar = 1;
    2. static int staticGlobalVar = 1;
    3. void Test()
    4. {
    5. static int staticVar = 1;
    6. int localVar = 1;
    7. int num1[10] = { 1, 2, 3, 4 };
    8. char char2[] = "abcd";
    9. const char* pChar3 = "abcd";
    10. int* ptr1 = (int*)malloc(sizeof(int) * 4);
    11. int* ptr2 = (int*)calloc(4, sizeof(int));
    12. int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
    13. free(ptr1);
    14. free(ptr3);
    15. }
    16. 1. 选择题:
    17.  选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
    18.  globalVar在哪里?____  staticGlobalVar在哪里?____
    19.  staticVar在哪里?____  localVar在哪里?____
    20.  num1 在哪里?____
    21.  char2在哪里?____  *char2在哪里?___
    22.  pChar3在哪里?____    *pChar3在哪里?____
    23.  ptr1在哪里?____     *ptr1在哪里?____

     globalVar在哪里?C  staticGlobalVar在哪里?C  staticVar在哪里?C  localVar在哪里?A  num1 在哪里?A  char2在哪里?A  *char2在哪里?A  pChar3在哪里?A   *pChar3在哪里?D  ptr1在哪里?A   *ptr1在哪里?C

    这些都是上述的答案,全部是关于各种类型的数据在C++中的存放位置。

    【说明】
    1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
    2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
    创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
    3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
    4. 数据段--存储全局数据和静态数据。
    5. 代码段--可执行的代码/只读常量。

    C语言中动态内存管理方式:malloc/calloc/realloc/free 

    1. void Test ()
    2. {
    3. int* p1 = (int*) malloc(sizeof(int));
    4. free(p1);
    5. // 1.malloc/calloc/realloc的区别是什么?
    6. int* p2 = (int*)calloc(4, sizeof (int));
    7. int* p3 = (int*)realloc(p2, sizeof(int)*10);
    8. // 这里需要free(p2)吗?
    9. free(p3 );
    10. }

    相信大家对malloc与calloc非常熟悉,唯一的区别就是参数不同,还有就是calloc给予开辟空间初始化,而malloc却没有。realloc是对calloc与malloc进行扩容的,扩容分为异地扩容与原地扩容,当目标位置空间足够时会进行原地扩容,反之如果不够将进行异地扩容。

    博主在之前的博客中详细讲解了C语言中的动态内存开辟,如果有疑问可以点击下面链接进行学习:C语言中动态内存管理方式:malloc/calloc/realloc/free icon-default.png?t=N7T8https://blog.csdn.net/m0_74755811/article/details/131820896?spm=1001.2014.3001.5501

    C++内存管理方式

    C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因
    此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。 

    new/delete操作内置类型

    1. void Test()
    2. {
    3.  // 动态申请一个int类型的空间
    4.  int* ptr4 = new int;
    5.  // 动态申请一个int类型的空间并初始化为10
    6.  int* ptr5 = new int(10);
    7.  // 动态申请10个int类型的空间
    8.  int* ptr6 = new int[3];
    9.  delete ptr4;
    10.  delete ptr5;
    11.  delete[] ptr6;
    12. }

    通过上述代码可以看出,使用new进行申请空间非常简单,只需要new加上类型即可。如果我们想进行初始化即可在后面加上(n)即可。当进行开辟多个内存空间时,我们像申请数组一样进行申请即可。但是在多个内存释放时,一定要加上[]。

    看到现在我们觉得malloc与new的功能差不多呀,最多就是少些一些字母,那为什么C++还要创造一个新的字符进行学习呢?我们接着往下看:

    new和delete操作自定义类型

    1. class A
    2. {
    3. public:
    4. A(int a = 0)
    5. : _a(a)
    6. {
    7. cout << "A():" << this << endl;
    8. }
    9. ~A()
    10. {
    11. cout << "~A():" << this << endl;
    12. }
    13. private:
    14. int _a;
    15. };
    16. int main()
    17. {
    18. // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
    19. //还会调用构造函数和析构函数
    20. A* p1 = (A*)malloc(sizeof(A));
    21. A* p2 = new A(1);
    22. free(p1);
    23. delete p2;
    24. // 内置类型是几乎是一样的
    25. int* p3 = (int*)malloc(sizeof(int)); // C
    26. int* p4 = new int;
    27. free(p3);
    28. delete p4;
    29. A* p5 = (A*)malloc(sizeof(A)*10);
    30. A* p6 = new A[10];
    31. free(p5);
    32. delete[] p6;
    33. return 0;
    34. }

     在内置类型中new与malloc是没有任何区别的,而在自定义类型中就会体现出极大的不同。在自定义类型中malloc不会对开辟的成员对象进行初始化,而new会自动调用构造函数。而在结束时delete会调研析构函数。所以说new与delete关键字就是为C++面向对象而产生的!!!

    operator new与operator delete函数

     new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
    系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
    operator delete全局函数来释放空间。

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

    通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
    malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
    就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

    那我们就会有疑问,new的底层逻辑就是malloc,delete的底层逻辑就是free。那么我们使用new开辟的空间能不能使用free进行释放呢?

    答案是:可以,但最好不要交叉使用。因为有时候程序会正常进行,但是有时候就会报错。这是为什么呢?

    当我们使用new申请一块非常简单的空间,只有一些基本的变量,最多就是少调用了一层析构函数,并不会影响空间的释放。但是当我们进行比如栈的开辟创建:

    1. class Stack
    2. {
    3. public:
    4. Stack()
    5. {
    6. cout << "Stack()" << endl;
    7. _a = new int[4];
    8. _top = 0;
    9. _capacity = 4;
    10. }
    11. ~Stack()
    12. {
    13. cout << "~Stack()" << endl;
    14. delete[] _a;
    15. _top = _capacity = 0;
    16. }
    17. private:
    18. int* _a;
    19. int _top;
    20. int _capacity;
    21. };
    22. int main()
    23. {
    24. Stack st;
    25. Stack* pst = new Stack;
    26. delete pst;
    27. return 0;
    28. }

    第一种情况是指针指向在堆开好的空间,只有两层关系,而使用new进行开辟先在堆上开辟对象空间,对象在使用构造函数进行初始化在堆上再开一层空间,是三层的关系。如果我们要使用free进行释放空间,只能将第二层进行释放,而第三层就产生了内存泄漏!!!

    所以我们不要交叉使用,做到一一对应!!!

    new和delete的实现原理

    内置类型

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

     自定义类型

    new的原理
    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
    delete的原理
    1. 在空间上执行析构函数,完成对象中资源的清理工作
    2. 调用operator delete函数释放对象的空间
    new T[N]的原理
    1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
    象空间的申请
    2. 在申请的空间上执行N次构造函数
    delete[]的原理
    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释
    放空间 

    定位new表达式(placement-new)

    定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
    使用格式:
    new (place_address) type或者new (place_address) type(initializer-list)
    place_address必须是一个指针,initializer-list是类型的初始化列表
    使用场景:
    定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
    果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

    1. class A
    2. {
    3. public:
    4. A(int a = 0)
    5. : _a(a)
    6. {
    7. cout << "A():" << this << endl;
    8. }
    9. ~A()
    10. {
    11. cout << "~A():" << this << endl;
    12. }
    13. private:
    14. int _a;
    15. };
    16. // 定位new/replacement new
    17. int main()
    18. {
    19. // p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
    20. 有执行
    21. A* p1 = (A*)malloc(sizeof(A));
    22. new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
    23. p1->~A();
    24. free(p1);
    25. A* p2 = (A*)operator new(sizeof(A));
    26. new(p2)A(10);
    27. p2->~A();
    28. operator delete(p2);
    29.  return 0;
    30. }

    C++与new的使用场景 

    虽然malloc与new都是在堆上进行开辟空间,但是他们获取内存的方式不一样。malloc是需要多少就索取多少,而new是”提前预支“内存,这样就可以提高new的效率,但是却导致了new空间浪费。所以说有利有弊,我们应该在适当的情况使用适当的做法。

    C++中使用malloc和new有不同的用途和行为,你可以根据需要选择哪个更适合你的情况。以下是一些情况下的推荐用法:
    使用malloc的情况:

    1.C兼容性: 如果你编写的是C++代码,并且需要与C库或其他C代码进行交互,使用malloc可能更合适,因为malloc是C标准库函数。
    2.需要手动管理构造和析构: malloc只分配内存,不会自动调用构造函数或析构函数。如果你需要手动控制对象的构造和析构过程,或者分配的内存不是用于存储对象(例如分配原始字节数组),则使用malloc。
    3.需要明确指定内存大小: malloc接受一个字节数作为参数,而new会考虑类型的大小和额外的构造函数开销。如果你需要确切控制内存分配的字节数,可以使用malloc。
    4.不需要类型检查: new是类型安全的,而malloc不是。如果你需要执行类型不安全的操作,可能需要使用malloc。

    使用new的情况:

    5.C++对象分配: 如果你需要分配内存以存储C++对象,通常应使用new或new[]。new会自动调用对象的构造函数,new[]用于动态分配数组,并在必要时调用构造函数。
    6.类型安全: new提供了类型安全性,可以避免一些常见的内存错误,例如内存泄漏和越界访问。
    7.更简洁的语法: new和new[]的语法更简洁,不需要显式指定分配的字节数。
    8.自动内存管理: new分配的内存会在对象的生命周期结束时自动释放,从而减少了内存泄漏的风险。

    总的来说,如果你编写纯粹的C++代码并需要分配内存以存储对象,通常建议使用new或new[],因为它们提供更好的类型安全性和内存管理。使用malloc通常是在需要更底层的内存分配控制,或者与C代码进行交互时的情况。无论使用哪种方法,都需要谨慎管理内存,确保在不再需要时释放它,以避免内存泄漏。


    以上就是本次全部内容,感谢大家观看!!! 

  • 相关阅读:
    自动化测试需知的4项测试工具!
    动态规划之背包问题
    声明 Array List 的3种方式 ArrayList、Collection、List 的区别
    MicroPython ESP32深度唤醒功能
    jQuery中显示与隐藏
    SaaSBase:什么是JIRA?
    22矩阵——向量范数和矩阵范数 : 矩阵范数与向量范数的相容、Numpy计算范数
    SpringAMQP
    广告制作如何高效完成视频视频审片?
    C++单例模式
  • 原文地址:https://blog.csdn.net/m0_74755811/article/details/134233462