当前位置:   article > 正文

C++的内存管理

C++的内存管理

前言

我们之前学习完了类和对象的相关内容,已经正式入门了。我们本节来学习一下C++的内存管理,看一看C++的语法和C语言的语法有什么区别和联系。那么废话不多说,我们正式进入今天的学习。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1. C语言和C++的内存分布

在开始学习之前,我们需要知道:C++的内存管理模式与C语言的内存管理形式保持一致。

通过回顾之前所学习过的知识,我们可以知道:C++和C语言程序内存区域是要进行划分的

内存管理存在的意义是:帮助计算机更加方便地处理各种各样的数据。那么不同的数据在内存之中又是如何划分的呢?我们知道:程序中会存在一些局部数据,需要建立栈帧,而且这一类数据都是用一会就要被销毁的;程序中还有一些长期存在的数据,例如全局数据、静态数据;程序中还有一些不能修改的数据,例如常量数据;程序中还有一些动态申请的数据。根据这些数据的使用环境和生命周期,C++和C语言将数据划分为以下几个段:

**************************************************************************************************************

内核空间

用户代码不能读写,内核空间使用场景较少,不做过多介绍

我们知道,函数的调用需要建立栈帧,栈帧就是存放在栈区域的。栈又叫堆栈,用于存放非静态局部变量、函数参数、返回值等等。栈是向下增长

内存映射段

内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。经常用于文件映射、动态库等,在Linux中应用广泛

堆用于程序运行时动态内存分配,内存中动态申请的数据就存放在堆中,堆是向上增长

数据段(静态区)

全局数据和静态数据就定义在数据段中

代码段(常量区)

用于存放常量以及编译好的指令

**************************************************************************************************************

下面我们来用一个题目深入理解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. }

选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)

(1)globalVar存放在哪里?____  

(2)staticGlobalVar存放在哪里?____

(3)staticVar存放在哪里?____  

(4)localVar存放在哪里?____

(5)num1存放在哪里?____

(6)char2存放在哪里?____  

(7)*char2存放在哪里?___

(8)pChar3存放在哪里?____      

(9)*pChar3存放在哪里?____

(10)ptr1存放在哪里?____        

(11)*ptr1存放在哪里?____

———————————————————————————————————————————

(1)C

globalVar是一个全局的数据,存放于数据段

(2)C

staticGlobalVar是一个全局的静态数据,故存放于数据段

(3)C

staticVar虽然是函数中的一个变量,但它通过static的修饰变成了静态变量,故存放于数据段

(4)A

localVar是Test函数中的一个临时变量,故存放于

(5)A

num1是一个数组名,这里代表的是整个数组,故存放于

(6)A

(注意:这里的 char2 开空间的时候会开5个字节,因为包含 \0)char2 是一个数组名,这里代表的是整个数组,表示在栈上开辟了一个五个字节的空间,并且将 abcd\0 拷贝过去

(7)A

做运算的时候数组名代表首元素的地址,所以 *char2 代表的是数组的首元素 a ,因为 a 是存放在上的,所以选A

(8)A

pchar3 虽然被const修饰,但 pchar3 是一个局部变量,故存在于

(9)D

*pchar3 指向的是 "abcd" 这个常量字符串,所以 *pchar3 在代码段

(10)A

ptr1 表示的是一个指针变量,所以仍然存放于

(11)B

*ptr1 指向动态申请的空间,故在

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

2. C语言中的动态内存管理方式

我们在C语言中用 malloc / calloc / realloc /free 来实现动态内存的管理,我们来回顾一下:malloc / calloc / realloc 三者的区别是什么?

malloc 函数可以用来动态申请空间,但是动态申请的空间不会被初始化

calloc 函数也可以用来动态申请空间,而且可以将其初始化,其功能相当于 malloc + memset

realloc 函数是用来扩容的,如果原扩容的空间不足的时候将会重新开辟一块新的空间,并将原空间内的所有数据内容拷贝至新的空间中,返回新空间的地址

free 函数用来释放动态申请的空间

———————————————————————————————————————————

我们来看一个问题:

  1. void Test ()
  2. {
  3. int* p2 = (int*)calloc(4, sizeof (int));
  4. int* p3 = (int*)realloc(p2, sizeof(int)*10);
  5. free(p3 );
  6. }

我们 free 完了 p3 还需要 free 掉 p2 吗?

答案是:不需要,因为 p3 存放的是 p2 扩容后的地址,扩容分为原地扩容和异地扩容,原地扩容的地址不变,而异地扩容会返回扩容后的地址,原地址就会被自动销毁,所以不需要释放 p2

———————————————————————————————————————————

我们可以通过下面的链接来扩展了解一下 malloc 的实现原理,不作硬性的要求:https://www.bilibili.com/video/BV117411w7o2/?spm_id_from=333.788.videocard.0&vd_source=416ea8b68b7adbc90fbd084c6cf37138

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

3. C++的内存管理方式

C++中可以继续使用C语言的内存管理方式,但是有些地方在使用C语言的内存管理方式的时候就会遇到一些问题,而且在使用的时候会比较麻烦,因此C++提出了自己的内存管理方式:通过new和delete操作符来实现动态内存的管理,下面我们就来介绍new和delete的使用方法

假设我们要申请一个类型为 int 的动态空间

如果使用 malloc 就需要传很多的数据,还需要强制类型转换,我们使用 new 就会方便很多,new 简化了 malloc 的操作步骤:

int* p1 = new int;

假设动态申请一个int类型的空间并初始化为10

int* p2 = new int(10);

假设动态申请10个int类型的空间

int* p3 = new int[10];

**************************************************************************************************************

如果我们需要释放动态申请的空间也很方便,但是我们需要注意,释放单个的空间和多个的空间的方法有些不同

释放单个动态申请的空间只需要在 delete 后直接加上指向该空间的指针变量即可;而释放多个动态申请的空间需要在 delete 后面加上一个 [] 再写指针:

  1. delete p1;
  2. delete p2;
  3. delete[] p3;

 **************************************************************************************************************

我们来具体讲解一下 new 的初始化:

刚才我们讲了单个对象的初始化,假设要进行多个对象的初始化,就需要采取下面的语法:

  1. int* p4 = new int[10] {0};
  2. int* p5 = new int[10] {1, 2, 3, 4, 5};

如果像 p5 一样采取的是不完全的初始化,那么未初始化的部分会默认初始化为0

(还有部分更复杂的初始化在下面会提及)

**************************************************************************************************************

学习到这里,我们对于 new 和 delete 的用法就有了基本的了解,那么C++设计 new 和 delete 仅仅是为了更加方便吗?还有没有其他的用处?

答案是肯定的,我们先来创建一个类A

  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. };

我们再 new 一个类型为 A 的动态空间:

  1. int main(void)
  2. {
  3. A* p1 = new A;
  4. A* p2 = new A(1);
  5. delete p1;
  6. delete p2;
  7. return 0;
  8. }

若此时我们运行程序就会发现,我们在动态申请一个自定义类型的空间的时候,自动调用了它的构造函数;而当我们用 delete 释放动态申请的空间的时候会调用它的析构函数

**************************************************************************************************************

…………………………………………………………………………………………………………………

当有了 new 和 delete 的时候,我们再去写链表等数据结构的时候就会非常的轻松:

  1. struct ListNode
  2. {
  3. int val;
  4. ListNode* next;
  5. ListNode(int x)
  6. :val(x)
  7. ,next(nullptr)
  8. {}
  9. };
  1. int main(void)
  2. {
  3. ListNode* n1 = new ListNode(1);
  4. ListNode* n2 = new ListNode(2);
  5. ListNode* n3 = new ListNode(3);
  6. ListNode* n4 = new ListNode(4);
  7. ListNode* n5 = new ListNode(5);
  8. n1->next = n2;
  9. n2->next = n3;
  10. n3->next = n4;
  11. n4->next = n5;
  12. return 0;
  13. }

…………………………………………………………………………………………………………………

我们现在接着上面的内容继续讲 new 的初始化:

刚才我们所讲的类中只含有一个参数,如果类A中含有两个参数,且含有默认构造函数

  1. class A
  2. {
  3. public:
  4. A(int a1 = 0, int a2 = 0)
  5. : _a1(a1)
  6. , _a2(a2)
  7. {
  8. cout << "A(int a1 = 0, int a2 = 0):" << this << endl;
  9. }
  10. ~A()
  11. {
  12. cout << "~A():" << this << endl;
  13. }
  14. private:
  15. int _a1 = 1;
  16. int _a2 = 1;
  17. };
  1. int main(void)
  2. {
  3. A* p1 = new A;
  4. A* p2 = new A(2, 2);
  5. A* p3 = new A[3];
  6. return 0;
  7. }

如上述代码,无论我们是给参数还是不给参数都能成功的完成初始化,而且 new n次,就会调用n次构造函数

但是假设没有默认构造函数呢?

我们来修改一下A类,将其改为没有默认构造函数的类:

  1. class A
  2. {
  3. public:
  4. A(int a1, int a2 = 0)
  5. : _a1(a1)
  6. , _a2(a2)
  7. {
  8. cout << "A(int a1 = 0, int a2 = 0):" << this << endl;
  9. }
  10. ~A()
  11. {
  12. cout << "~A():" << this << endl;
  13. }
  14. private:
  15. int _a1 = 1;
  16. int _a2 = 1;
  17. };

那么我们该修改下面的代码,让它能够成功定义并且初始化呢?

	A* p3 = new A[3];

答案是:我们需要先定义三个对象,再把这三个对象作为参数传递给动态申请的空间

  1. int main(void)
  2. {
  3. A aa1(1, 1);
  4. A aa2(2, 2);
  5. A aa3(3, 3);
  6. A* p3 = new A[3]{ aa1,aa2,aa3 };
  7. return 0;
  8. }

但是如果像这样定义对象来初始化就会比较麻烦,而且严格意义上来讲这里调用的就不是构造函数了,而是拷贝构造函数,因为 aa1、aa2、aa3 是已经存在的对象。所以此时我们就可以换一种写法,用匿名对象来初始化

  1. int main(void)
  2. {
  3. A* p4 = new A[3]{ A(1,1),A(2,2),A(3,3) };
  4. return 0;
  5. }

这种写法还会被编译器优化,就只需要调用构造函数就好了,而不用再调用拷贝构造函数了(详情见上一节的类和对象收尾)

除了这两种写法还有第三种写法:

  1. int main(void)
  2. {
  3. A* p5 = new A[3]{ {1,1},{2,2},{3,3} };
  4. return 0;
  5. }

 我们之前学习过了:单参数构造函数支持隐式类型转换,而多参数构造函数也支持隐式类型转换,第三种写法在本质上和第二种写法是一样的,这种写法也会被编译器优化,会转变为直接构造,而不去调用拷贝构造

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

4.C++的抛异常(精简) 

之前在学习C语言的时候,我们在 malloc 结束以后通常都会用 perror 检查申请空间是否成功。而我们在 new 一个动态空间的时候通常不用 perror 去检查,那么C++为什么不去检查呢?此时我们就要提前引入一个抛异常的概念(这里只作简单的说明,具体的细节后面会详细讲解)

在C语言中如果 malloc 失败了就会返回空指针,而在C++中 new 失败了并不会返回空指针,new 失败了会抛异常

抛异常由三个关键字组成:throw、try、catch

throw:发生异常了以后用 throw 抛出一个对象

try / catch:对异常进行处理

                                             (面向对象的语言通常都会有抛异常的步骤)

我们先来看一下申请空间失败了是怎么抛异常的

因为在正常的情况下,我们动态申请的空间都很小很小,很难开辟空间失败抛异常,所以这里我们一次申请1GB的内存空间(32位下):

  1. int main(void)
  2. {
  3. void* p1 = new char[1024 * 1024 * 1024];
  4. cout << p1 << endl;
  5. void* p2 = new char[1024 * 1024 * 1024];
  6. cout << p2 << endl;
  7. void* p3 = new char[1024 * 1024 * 1024];
  8. cout << p3 << endl;
  9. return 0;
  10. }

此时我们就需要用到 try、catch 就可以大致知道发生了什么错误(此部分仅作大致了解即可):

  1. int main(void)
  2. {
  3. try
  4. {
  5. void* p1 = new char[1024 * 1024 * 1024];
  6. cout << p1 << endl;
  7. void* p2 = new char[1024 * 1024 * 1024];
  8. cout << p2 << endl;
  9. void* p3 = new char[1024 * 1024 * 1024];
  10. cout << p3 << endl;
  11. }
  12. catch (const exception& e)
  13. {
  14. cout << e.what() << endl;
  15. }
  16. return 0;
  17. }

bad allocation 的意思是内存申请失败了,没有足够的内存

若是像下面这种情况,当在函数中申请内存失败了以后就不会继续往函数下面走了,而是直接跳到 catch 的地方去:

  1. void func()
  2. {
  3. void* p1 = new char[1024 * 1024 * 1024];
  4. cout << p1 << endl;
  5. void* p2 = new char[1024 * 1024 * 1024];
  6. cout << p2 << endl;
  7. void* p3 = new char[1024 * 1024 * 1024];
  8. cout << p3 << endl;
  9. }
  10. int main(void)
  11. {
  12. try
  13. {
  14. func();
  15. }
  16. catch (const exception& e)
  17. {
  18. cout << e.what() << endl;
  19. }
  20. return 0;
  21. }

严格意义来讲,写C++的程序的时候都需要 try 和 catch 来对异常进行处理

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

5. operator new 与 operator delete 函数

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

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间 失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常

我们先来看一下 operator new 的底层代码:

  1. void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
  2. {
  3. // try to allocate size bytes
  4. void* p;
  5. while ((p = malloc(size)) == 0)
  6. if (_callnewh(size) == 0)
  7. {
  8. // report no memory
  9. // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
  10. static const std::bad_alloc nomem;
  11. _RAISE(nomem);
  12. }
  13. return (p);
  14. }

由上述的代码可以知道,当申请内存空间失败的时候,就会抛出 bad_alloc 类型的异常。bad _alloc 是 exception 的子类

这里不做详细的讲解,我们需要学习过继承与多态以后才能知道,只需要了解即可

上述代码中的 RAISE 在底层是一个宏,RAISE 就是 throw 即当 malloc == 0 的时候就会抛异常。通过这些我们就可以知道,其实 new 的底层还是 malloc

下面我们来看一下 delete 的底层实现:

  1. void operator delete(void* pUserData)
  2. {
  3. _CrtMemBlockHeader* pHead;
  4. RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
  5. if (pUserData == NULL)
  6. return;
  7. _mlock(_HEAP_LOCK); /* block other threads */
  8. __TRY
  9. /* get a pointer to memory block header */
  10. pHead = pHdr(pUserData);
  11. /* verify block type */
  12. _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
  13. _free_dbg(pUserData, pHead->nBlockUse);
  14. __FINALLY
  15. _munlock(_HEAP_LOCK); /* release other threads */
  16. __END_TRY_FINALLY
  17. return;
  18. }

delete 的这一大段代码我们暂时不需要去关注,我们只需要关注最重要的一行代码:

_free_dbg(pUserData, pHead->nBlockUse);

那么这里的 _free_dbg 和 free 的关系是什么呢?我们来看一下 free 的实现:

#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

由此我们知道,free 是一个宏函数,free 的底层也是调用的 _free_dbg ,所以我们也可以知道 delete 的底层是 free

学到这里我们可能会有疑问,new 不是一个函数调用,那又是如何转换成 operator new 的呢?其实是编译器在编译的时候直接把它生成对应的指令

我们先来看看 new 的反汇编

其中下面的代码表示的是申请空间,如果申请失败就抛异常:

而这里的代码则表示调用构造函数

我们再来看看 delete 的反汇编:

我们在看 delete 函数的反汇编时,并没有直接的看到 _free_dbg ,而是调用了析构函数

来调用完了析构函数再调用 operator delete 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

6. 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来释放空间

在使用 delete[] 的时候我们不需要自己去传参数

  1. int main(void)
  2. {
  3. A* p1 = new A(1);
  4. delete p1;
  5. A* p2 = new A[5];
  6. delete[] p2;
  7. return 0;
  8. }

此时我们可能会有疑问:假设我们使用语法不匹配会发生什么? 

假设我们用 free 来释放 new 出来的空间

  1. int main(void)
  2. {
  3. int* p1 = new int;
  4. free(p1);
  5. return 0;
  6. }

我们可以看到,这里并没有出现什么问题,也不存在内存的泄露,因为这里的代码是内置类型的数据,不涉及调用析构函数。那如果这里的数据类型使用一个自定义类型呢?

  1. A* p2 = new A;
  2. free(p2);

此时使用 free 相比较于 delete 少调用了析构函数,如果自定义类型的析构函数涉及释放动态开辟的空间的时候,就会出现内存泄漏

所以使用语法不匹配可能会出现内存泄漏的风险

那么如果我们是 delete 和 delete[] 使用不当呢?

此时我们再来创建一个类B

  1. class B
  2. {
  3. private:
  4. int _b1 = 1;
  5. int _b2 = 2;
  6. };
  7. int main(void)
  8. {
  9. int* p1 = new int[10];
  10. delete p1;
  11. return 0;
  12. }

对于上述代码中的内置类型而言,这里仍然不会出现问题。因为 new 在底层就是调用了 malloc 函数,因为内置类型没有涉及调用构造和析构函数,所以这里不会出现问题。

但是如果是自定义类型

  1. int main(void)
  2. {
  3. B* p2 = new B[10];
  4. delete p2;
  5. return 0;
  6. }

当自定义类型为B的时候,我们发现这里还是没有出现问题,我们把类型改成A:

  1. int main(void)
  2. {
  3. A* p3 = new A[10];
  4. delete p3;
  5. return 0;
  6. }

此时我们就发现程序崩溃了。A和B都是自定义类型,但是为什么B不会崩溃而A就崩溃了呢?

这里我们就需要从底层进行分析:我们先看到 A 类型的对象和 B 类型对象都是8个字节

我们转到反汇编,这里 new 在底层调用的是 operator new ,因为一个B类型的对象是8个字节,所以这里需要申请80个字节

但是我们申请了10个A对象的空间发现此时的空间大小不是80个字节了,而是84个字节,这是为什么呢?

当编译器开辟多个自定义类型的对象的数组的时候,会在开辟的空间前面多开辟4个字节,这开辟出来的4个字节用于存储对象的个数

而且我们 new A 的时候返回来的地址不是这个存放对象个数的4个字节的地址,而是向后偏移4个字节的地址。此时直接释放就会导致不完全释放,而B中指针指向的就是它的起始位置,它没有额外多申请那4个字节的空间,所以不会存在问题

那为什么A开辟了4个字节存储对象个数,而B没有多开辟4个字节去存储个数呢?严格意义上来说A和B都需要开空间去存储对象的个数,B没有开空间是因为编译器发现B没有写析构函数所以将其优化了

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

7. 定位new表达式(placement-new)

通过刚才的学习我们知道,我们写 new 的时候,编译器会自动帮我们调用 operator new 并且调用构造函数,其实我们也可以自己手动去调用 operator new 但是它不会自动帮我们调用构造函数:

  1. int main(void)
  2. {
  3. A* p1 = new A(1);
  4. cout << "**********************************" << endl;
  5. A* p2 = (A*)operator new(sizeof(A));
  6. return 0;
  7. }

可以看到:p2 只开了空间,而没有调用构造函数。假设我们现在需要对一块已经存在了的空间显示的调用构造函数,此时定位 new 就可以帮助我们完成这个功能。接下来我们来讲解一下定位 new 的使用方法:

假设 p2 是一个已经存在了的空间,我们要对 p2 调用构造函数只需要采取以下的语法:

  1. int main(void)
  2. {
  3. A* p1 = new A(1);
  4. cout << "**********************************" << endl;
  5. A* p2 = (A*)operator new(sizeof(A));
  6. new(p2)A(1);
  7. return 0;
  8. }

假设我们要调用析构函数呢?

这里我们需要注意,调用构造函数是只能通过定位 new 来调用的,但是调用析构函数,是可以直接显示调用的:

  1. int main(void)
  2. {
  3. A* p1 = new A(1);
  4. cout << "**********************************" << endl;
  5. A* p2 = (A*)operator new(sizeof(A));
  6. new(p2)A(1);
  7. delete p1;
  8. p2->~A();
  9. return 0;
  10. }

或者也可以写 operator delete(p2)

有人可能会觉得这个动作多此一举,直接用 new 和 delete 更加方便,为什么还要专门设计一个定位 new 呢?感觉这个语法有点多此一举。

其实这个定位 new 在我们日常生活中写代码的时候基本上不需要使用,但是某些特定的场景下使用定位 new 会非常好。后面我们会学习到STL中有一个内存池的概念

内存池:我们首先需要知道一个技术叫做池化技术,池化技术的意思是我们可以建造一个“池子”,将某些资源存入这个池子中,使用的时候就会更加方便、更快。池化技术有利于提升性能,常见的池化技术有:内存池、线程池、连接池……

我们以内存池为例:假设我们需要高频的申请和释放内存块,此时我们就可以专门建造一个内存池,内存池里面的空间仍然是在堆中来的,但是内存池里面的空间是专供使用的,别的地方是使用不了的,此时效率就会更加高。但是此时就会有一个问题,内存池只能给空间而不能调用构造函数进行初始化,此时定位 new 就有它存在的意义了

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

结尾

C++的内存管理和C语言的内存管理模式基本一样,所以上手比较快,那么关于C++的内存管理的所有内容就到此结束了,下一节我们将学习C++模板,模板是真正让C++和C语言拉开差距的语法。希望本节的内存管理内容可以给你带来帮助,谢谢您的浏览!!!

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/876090
推荐阅读
相关标签
  

闽ICP备14008679号