C++11、17、20的内存管理-指针、智能指针和内存池从基础到实战(中)
创始人
2024-05-26 13:32:20
0

C++11、17、20的内存管理-指针、智能指针和内存池从基础到实战(中)

  • 第三章 分配器allocator和new重载
    • 1、重载operator的new和delete包括数组
      • 如果我们访问的是一个数组
    • 2、类成员操作符new重载和放置placement_new
      • placement new(放置内存)
    • 3、分配器allocator详解c++17_20新特性说明
    • 4、自定义allocator演示vector和list分配器
      • 现在换成我们自定义的分配器
      • 我们再试一个list容器
    • 5、未初始化内存复制分析uninitialized_copy
    • 6、c++17 20 construct对象构造和销毁
  • 第四章 C++指针与面向对象
    • 1、限制栈中创建对象和调用delete销毁对象
    • 2、类继承和多继承内存地址分析
      • 多继承内存分析
    • 3、多继承中的二义性和虚基类内存问题分析
      • 虚基类、虚继承
    • 4、虚函数原理和内存分析
    • 5、虚函数表指针直接访问函数的代码实验
      • 手动调用虚函数表里面的函数
  • 第五章 C++17内存池
    • 1、c++17内存池memory_resource内存池原理
    • 2、c++17内存池synchronized空间申请源码分析
      • 写代码测试内存池
    • 3、c++17内存池空间释放代码分析


第三章 分配器allocator和new重载

1、重载operator的new和delete包括数组

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以看到我们的TestMem类里面没有成员,没有成员的话它new出来一个空间它至少也要占一个字节,也就是说它new出来一个对象,至少分配的是一个字节;
如果给它添加一个int成员:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果是new一个int类型呢:
在这里插入图片描述

如果我们访问的是一个数组

我们申请一个int类型的数组空间,和一个TestMem类型的数组:
在这里插入图片描述

在这里插入图片描述

我们把new一个普通的对象和new一个数组的操作符区分开来;
我们重载操作符new访问数组的函数:
在这里插入图片描述

在这里插入图片描述

我们new函数是在进入构造函数之前先进入的, 因为你得先有空间,才能够做构造,因为构造肯定是在你的内存空间基础之上来做构造的。

我们重载清理的函数:
在这里插入图片描述

在这里插入图片描述

我们从上图可以看到,进入构造函数,然后进入析构,析构完了之后最后才调用的delete,因为析构的时候其实是还能够访问内存的,所以这时候你不能把空间删掉,基本上是在析构之后调用的delete,也就是说只要进入到我们delete函数,表示这个对象已经析构过了。

2、类成员操作符new重载和放置placement_new

对于大部分项目来说,重载全局的new其实是很危险的,特别是你做了一些特定操作的时候,当然你可以在debug模式重载一下,用来记录所有的内存的申请和分配;
很多情况下,我们只希望是在某一个类的时候,它的new出来的这个过程我们去控制,而针对其他类的内存我们不用去控制。
在这里插入图片描述

在这里插入图片描述

但是我们发现在操作数组的时候,我们是调用了全局的数组函数,那也就是说我们要把这个数组函数进行重载:
在这里插入图片描述

在这里插入图片描述

当你调用、生成这个TestMem对象的时候,它优先找我们类当中的成员,找不到的话再去找你重载的new,再找不到的话就去找全局的、本身的new。
我们把delete和delete[]也在TestMem类当中进行重载:
在这里插入图片描述

在这里插入图片描述

placement new(放置内存)

在这里插入图片描述

在一些特定的需求当中,我们要分配一个对象的时候,我们让它在以后的空间当中创建;
或者是我们把空间的创建和对象的构造给它分割开来,这样可以加快我们的效率;
当然还有一种情况,我们想new出一个在栈当中分配的空间,但是这个方式使用的时候我们要注意的一点,我们这个new调用之后,它生成的空间是不会释放的,也就是说不需要你去释放,因为这个空间你并没有生成,你只是在指定的空间当中做了对象的初始化。

在栈当中分配空间,其实我们就不需要对它进行释放了,注意啊,你准备好的这个空间要大于你待会要放置的空间:
在这里插入图片描述

我们创建的这个对象是在原来的buf1的地址当中,我们可以试着把这个地址打印一下:
在这里插入图片描述

在这里插入图片描述

那这个时候我们能不能清理呢,delete是不能清理mem2的,因为这是在栈中调用的。

那如果说要在堆当中调用呢?
在这里插入图片描述

在这里插入图片描述

那我们析构该怎么调用呢?
如果我们是在栈中调用的,析构是没法调用的,如果我们delete这个栈当中的空间的话:
在这里插入图片描述

在这里插入图片描述

清理栈当中的空间那么程序肯定会当掉的。

我们的析构要自己调用,也就是说析构函数我们自己主动调用:
在这里插入图片描述

在这里插入图片描述

我们也可以对placement new进行重载(对应类当中成员的),只不过会包含两个参数:
在这里插入图片描述

也就是说它可以在现有的空间当中来分配我们new的空间,就相当于我们这个空间就不用申请了,但是可以构建它的过程。
在这里插入图片描述

在这里插入图片描述

3、分配器allocator详解c++17_20新特性说明

在这里插入图片描述

我们用标准分配器主要来实现什么呢?
也就是说把对象内存的创建和构造函数的调用给它分割出来,为什么要分割呢?
有时候我们创建对象的时候,在内存分配的时候我们也许一上来分配了1000个对象,但是在业务逻辑当中呢,我们没必要在分配这1000个对象的时候就全部给它们初始化,所谓初始化其实最重点的就是会调用它的构造函数,那我们只有在用到某一个对象的时候,我们内存要提前分配,但是我们再用某一个具体对象的时候再对它进行初始化。
我们刚才讲了,分配器除了把算法和存储细节隔离开之外,它同样可以实现对象内存分配和构造分离;
正常情况我们如果new一个对象出来,它的分配和构造一起做的,有时候我们要把它分开了来做,我们也是通过分配器来实现。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总的来说就是根据你的类型大小,new出来一块size这么大的空间。
我们编译运行看看:
在这里插入图片描述

可以看到内部调用的时候,它其实调用的是new函数,它并没有构造,也就是说这一步是不构造对象的。
在这里插入图片描述

在这里插入图片描述

你可以看到其实的过程,它也没有调用析构函数,所以说在我们使用这个内存分配的时候,就需要我们主动地去控制什么时候调用构造和析构,也就是说析构的调用权也交给了你。

那我们怎么去构造呢?比如说我们拿到了这块空间,那怎么去调用它的构造函数?
在这里插入图片描述

在这里插入图片描述

我们可以看到构造函数就被调用了。
在这里插入图片描述

在这里插入图片描述

4、自定义allocator演示vector和list分配器

在这里插入图片描述

在这里的通用分配器有时候是外部条件传进来的,包括我们也可以去实现一些算法内部提供的分配器,也就是说我们通过外部传递然后来实现分配,这样把整个的空间分配和业务逻辑做这样的一个解耦合。

我们可以看一下现有的vector是怎么做的,然后我们再来看我们怎么去实现这样一个自定义分配器。
在这里插入图片描述

在这里插入图片描述

因为vector里面存放的是XData,不是指针,我们存的是对象,所以说这个push_back它每次其实会复制一份到它内部的内存。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到全是Drop,我们为了监控它的复制,给XData添加一个拷贝构造函数:
在这里插入图片描述

在这里插入图片描述

编译出错,拷贝构造函数的参数是要加const的。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

从上图我们可以看到,再次Copy 111的时候,它可能重新划分了一个空间,然后把之前的(第1个XData 111)给释放掉,然后后面继续拷贝;
我们修改for循环里面的代码,把xd换成引用,否则我们在这里面读取的时候又做了一次拷贝,我们换成引用的话减少它的拷贝。
在这里插入图片描述

在这里插入图片描述

这样输出就清晰一点,其实它做了一次内存的移动,把整个的空间增大之后,要把原来的空间清理掉。
所以说它整个是这样一个过程:
首先,第一次创建的时候,它可能预先分配好空间,当空间不够的时候,它会申请一块更大的空间,然后把前面的空间复制过来,并且把前面空间释放掉,有这样一个过程。

在这里插入图片描述

在这里插入图片描述

现在换成我们自定义的分配器

在这里插入图片描述

我们可以看到vector是有两个模板参数的,第一个参数是容器的类型,第二个参数是allocator,也就是说vector它默认使用了allocator的分配器。
那这时候我们把它换成自定义的allocator分配器,这里面不要涉及继承,因为这是模板函数,所以也不存在继承的概念。

模板函数就是声明跟定义不能分开了,声明跟定义只能在一起:
在这里插入图片描述

在这里插入图片描述

我们先编译一下看看错误:
在这里插入图片描述

可以看到它有一些特征,它用到了一个叫做value_type,因为它里面的代码会用value_type去做一系列的事情。

在这里插入图片描述

在这里插入图片描述

这里面的函数名称是固定的,如果你写错名称的话,它的调用会找不到这个函数,也就是说在我们的vector当中它会调用这里面相关的函数,所以名字要正确。

我们编译一下可以看到错误:
在这里插入图片描述

在这里插入图片描述

因为vector也是一个模板容器,它其实不知道我们这个MyAllocator中的Ty类型,vector去访问的时候它里面自动生成的代码都用的叫value_type,也就是说它会用value_type去作为类型的方式,因为vector本身支持任意的容器进来,所以说它的类型名不固定,那这时候我们就把所有的类型名都给它指定成value_type,它后面的代码都可以用value_type就可以实现我们这样一个隔离分开的功能,所以说这个value_type要加进来。
我们再编译一下:
在这里插入图片描述

它这个错误是在析构部分。

在它内部的实现要替换成另外一个类型,当然我们这个函数可以什么都不做,因为这里我们用不到,如果说要用到的话,我们还需要再重载另外一个,把当前的分配器指向另外一个分配器,虽然用不到,但是vector内部实现的代码有调用,所以我们得提供它,提供个空的就可以了。
在这里插入图片描述

在这里插入图片描述

因为我们添加了一个有参数的构造函数,所以需要补充默认的构造函数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们给分配和释放添加上输出代码,来监控一下整个过程,它什么时候来跟我们申请的空间:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到,第一次申请了一个空间,第二次申请了两个空间的时候,它会把前面的空间复制到我们新的空间当中去;
allocate 3,又做了一次复制,当然了,整个的减少复制过程的流程就需要你自己来做了。
我们每次push_back的时候其实会申请一块新的内存空间,第一次申请1个,第二次申请2个,第三次申请3个,其实这样做的效率是不高的,所以这个优化过程由你自己来写这样一个算法,比方说最大容量放多少,第一次一上来就给它申请1024个,到后面申请的时候直接返回这个空间的地址就可以了,也就是说第二次申请的时候就不需要再给它新的空间,这个根据你的业务逻辑来,这里我们只要把流程给它跑通就可以了。

我们可以把类型打印出来看看:
在这里插入图片描述

在这里插入图片描述

我们可以看到,第一次它先往容器里面加了一个Container_proxy,通过这样一个分配器我们也是可以进一步跟进到容器的代码当中。

我们再试一个list容器

在这里插入图片描述

list跟我们上面的vector不同,vector就是一个固定的数组,而list它是一个链表结构,它的类型申请其实是每push一个内容,它都会去申请一块内存。
在这里插入图片描述

list每次分配的是1个空间(allocate 1),所以你在做分配器的时候就要考虑到它的这个分配空间。

我们给for循环里面的代码换成引用(因为auto它不会自动推导出引用):
在这里插入图片描述

在这里插入图片描述

5、未初始化内存复制分析uninitialized_copy

在这里插入图片描述

我们把一个对象复制到一块未初始化的内存当中,什么叫未初始化的内存,malloc直接申请的内存就算是,或者是你明明要存放很多的对象,但是你是new出来的一个char的空间,并不是new出来整个对象;比方说你new 1024个XData对象,那这个就是已经初始化过的,所以不需要你去做相应的处理,而我们很多情况下,我们拿到了一组对象,我们希望把它复制到一个我们本地的内存当中去,这是经常涉及到的数据交互。
很多做法怎么做呢?
如果是C语言编码的话,那就memcpy,如果是C++的话,我们可能用std::copy直接把整个内存复制过去就可以了;

那为什么我们有时候会用到uninitialized_copy这样一个未初始化的复制方法呢?
它会调用拷贝构造,它会把对象进行复制。

在这里插入图片描述

在这里插入图片描述

我们希望把上面的datas复制到我们buf当中,并且确保在这个buf当中存储的对象是经过初始化的,或者是调用过拷贝构造的,那我们一种做法是直接调用memcpy。
但是这种有什么问题呢?
在这里插入图片描述

我们可以看到它没有调用拷贝构造,这里面就有问题了,如果我们这个XData里面有指针指向的空间,有个堆空间,那这时候这块空间可能就是无效的了,通过memcpy就做不到拷贝构造,为了解决问题,我们手动来得做一遍复制。

我们再来看看C++的std::copy方式:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到,std::copy同样也没有调用我们的拷贝构造函数,也就是说它不是生成一个新的对象,它是做的一个把内存空间整个复制的过程。

那我们如果希望它重新构造呢?
在这里插入图片描述

在这里插入图片描述

我们把赋值操作符等于符号=进行重载:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们uninitialized_copy这种方式是把整个对象做了初始化,并且以传进来的值作为拷贝构造。

6、c++17 20 construct对象构造和销毁

之前我们的构造是调用了C++11开始就支持的allocator_traits里面的方法来获取了对象,在C++17和C++20呢也提供了一个简化的方法来调用构造函数。
为了使用C++17和C++20要设置项目属性、常规、C++语言标准为预览 - 最新C++工作草案中的功能
在这里插入图片描述

接着我们写代码,分配一块空间,在现有的空间中构造对象:
在这里插入图片描述

我们可以看到调用了构造函数,但是退出之后并没有释放这些对象,因为这里面我们是主动调用了构造函数,因为空间是在malloc中申请的,所以最后的话我们要free把空间释放了,当然用free释放的话,是不会调用对象的析构函数的:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到C++20的destroy的两个参数是像迭代器一样的指针,表示开始指针位置和结束指针位置:
在这里插入图片描述

在这里插入图片描述

上图这是由于我们在for循环中构造对象的时候下标错了,应改为:
在这里插入图片描述

在这里插入图片描述

这样的话就完成了构造和析构,当然我们在构造的过程中可以把值进行修改。

在这里插入图片描述

我们看到上图绿色波浪线提示异常:data有可能为NULL,所以我们修改代码对data做个判断来排除异常:
在这里插入图片描述

第四章 C++指针与面向对象

1、限制栈中创建对象和调用delete销毁对象

本节课我们来讲一下指针与面向对象的关系,也就是说我们通过指针去操作类当中的成员,包括虚函数它的空间分配情况,我们重点就是指针操作内存,我们看一下在类当中它的内存情况。

我们先来做第一个示例,我们限制在栈当中创建对象,并限制你用delete来销毁对象。

我们为什么要做这样一个限制呢?
你可能会看到很多的工厂方法当中,它会要求你必须用它的接口来创建对象,这也是在设计模式当中经常会用到的,包括单件模式我们也会用到限制栈中创建对象,因为栈中创建的空间很多你是具有不确定性的,就是当你限制用户的时候,就是怎么让使用你代码的人不去在栈中创建对象,怎么达到这个目的呢,你可以写在文档当中、写在说明当中,但是这样的做法大部分人是不看文档的,他先用、先跑起来就不管到底是怎么跑起来的;
所以我们这时候对他做一个限制的话,那我们要在语法层面就让他过不了,也就是说如果他在栈中创建对象,我让他编译不过,那我们怎么通过代码来实现让他不能在栈中创建对象,也就是说只能在堆中创建对象,当然了,我们也限制他在堆当中创建对象必须通过我们的接口来进行调用。
其实这里面我们不是限制堆或者栈,而是说我限制你自己创建对象,必须经过我的接口,这样的话我们在接口当中就可以做统一的处理。
主要做哪些处理呢?
比如说我们是做组合的设计模式,把多个对象拼在一起,增加一些属性,这样的话我们可以做出很多的特性,而不去跟我们具体的类的构造函数相关,因为在构造函数当中我们肯定不能做太多事情,就是在外部来达到这样的目的;
当然了,因为涉及到我们待会会做protected,可能会涉及到友元,那么我们这边尽量简化在当前类当中去创建对象。

在这里插入图片描述

在这里插入图片描述

现在默认肯定是编译正确通过的,如果说我们想要限制它,不允许像上图选中那样做,一旦这样做就让它编译出错。

把构造函数做成protected或者私有的:
在这里插入图片描述

一旦把构造函数放到protected当中,你在外部调用、生成这个对象的时候,因为生成对象的时候是要调用构造的,而构造函数是protected的,只能是它的派生类或者友元才能访问,所以这时候在外部就不能访问。
那它怎么创建对象呢?就要通过我们提供的接口来创建了。

在这里插入图片描述

静态成员函数和全局函数的一个区别就是,它能够访问类的成员,包括私有成员和保护类型的成员,包括成员函数,就是这么一个重要的区别。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们尽量把析构函数做成虚函数,构造函数就不需要做成虚函数了,构造函数不存在虚函数,析构函数要做成虚函数,因为你如果说是用派生类的对象来访问的时候,你可能清理不掉。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

整个的清理过程都经过我们内部的方法,这样就限制了在外部直接调用delete,因为你在内部进行清理的时候,你就可以做很多的检查判断。

2、类继承和多继承内存地址分析

在这里插入图片描述

其实对象的重点就是它里面存的是什么,就是存的成员变量, 函数的话其实是在代码区;
在堆区或者栈区生成一个对象,它的空间里面存放的都是对应的成员变量,代码肯定不会在这里的,当然它会存一个虚函数表。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

错误的理解,会以为派生类A的x1会覆盖基类B的x1,其实不存在覆盖,这两个变量的空间都会存在。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

整个派生类A的对象的首地址,里面存的先是基类的成员,然后再到派生类,这就是单继承的内存分布。

多继承内存分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过这些代码,我们也基本上推导出它的内存空间的分配情况。

3、多继承中的二义性和虚基类内存问题分析

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
Base1和Base2都包含有C:c1,而A又继承于Base1和BAse2,同一个类的成员C:c1有两份,这时候在代码当中你是没法区分它们的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

现在编译是没有问题,如果我们通过A3这个对象去访问c1的时候,可以看到异常提示:“A3::c1 不明确”:
在这里插入图片描述

我们把类C加进来限定一下:
在这里插入图片描述

此时你说赋的哪份c1的值呢?这时候我们应该是有两份c1的。
我们可以把地址打印出来:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以看到它其实取到的是B1的c1,这样操作会带来很大的歧义性问题,我们变得不可知,当涉及到调用一些函数的时候,它访问成员的时候,那究竟是访问的哪一个成员,你就没法确定,所以我们其实希望的是c1只有一份。

虚基类、虚继承

在这里插入图片描述

这时候我们称C为虚基类。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到所有的c1是在同一个地址下面,这样就确保了我们只存储了一份。

那你说它是怎么实现的这个功能呢?怎么能确保它只存了一份呢?我们这个虚继承究竟做了什么事情呢?
这是在编译器层面就解决了的,编译器在整个编译过程当中就能够知道你是一个虚继承,它本身也可以在继承的过程当中存放这个空间,因为你编译器必须要指定这块内存的分配方式。

4、虚函数原理和内存分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到它访问的是B的test1,但是我们传进去的是一个A的对象,我期望它调用的是A的test1,因为经过函数参数的类型转换,转换成基类的指针,因为我们在设计当中经常会这么做。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

它也是调用的B的test1,而我们期望传递进去是C的对象,它就应该调C的函数,我们传递的是A对象,它就应该调用A的函数,但实际情况不是这样的,那这时候我们怎么处理呢。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过设定基类函数为虚函数,就解决了我们传递子类类型转成基类之后,必须要调用实际对象的方法。
你那说在你不定义虚函数的时候,它为什么找不到呢?
在这里插入图片描述

在这里插入图片描述

5、虚函数表指针直接访问函数的代码实验

我们来看一下两个同一类型的不同对象,指向的虚函数表是否是同一块地址:
在这里插入图片描述

在这里插入图片描述

A类继承的是B,那么B的虚函数表的地址跟A的一样么?不一样的,不同类指向的是不同的虚函数表。

手动调用虚函数表里面的函数

我们通过虚函数表调用里面的函数,因为我们没法把这样的一个int类型转成thiscall类型的函数指针,所以只能做一个普通函数,这样调用也没有问题。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第五章 C++17内存池

1、c++17内存池memory_resource内存池原理

在这里插入图片描述

2、c++17内存池synchronized空间申请源码分析

在这里插入图片描述

在这里插入图片描述

写代码测试内存池

我们先创建好了内存池,然后在内存池中创建1000个小的数据块,把申请数据的指针存到vecor当中,再把它释放了;然后再创建大的数据块,我们来看看小数据块和大数据块在代码当中的一个区别。
实际当中我们什么时候会用内存池,用内存池一定是它的空间不确定的,每次申请的空间不固定,如果空间固定的话那我们就不一定要用内存池了,我们固定的用一个链表自己来运维反而更简单。
当然,这里测试的时候可以让它空间是固定的。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

vs2019有内存分析的诊断工具,从右图诊断工具那里可以看到申请了1G的内存,申请速度有点快,我们把速度放慢点,这样可以看到申请的过程:

在这里插入图片描述

在这里插入图片描述

我们观察内存诊断工具中的进程内存快照,可以看到内存的曲线图是成倍加大的,内存是以指数级往上翻倍的,从100M(实际的数值可能是142M),200M(285M),400M(570M)这样子增长,直接翻到了1G(右图的数据每次多一些);
它这样的好处是减少了你的内存分割,如果你是用new和delete,你可能会不断地申请不断地释放,会有大量的内存碎片,并且申请过程的消耗比较大;
它这样的好处是,你每次申请空间的时候,其实这个空间已经申请好了,只是返回一个地址而已,那这个效率肯定会高很多,特别是对于一些数据量比较大的操作。

测试内存溢出的情况:
在这里插入图片描述

在这里插入图片描述

可以看到可以抛出异常。

我们来看下源码:
在这里插入图片描述

我们的do_allocate是线程安全的内存池的:
在这里插入图片描述

在这里插入图片描述

线程安全的线程池的do_allocate,其实是调用了非线程安全的线程池的do_allocate,只不过在这里面加了一个锁,也就是说进入这个函数它肯定是线程安全的。
我们再进入到非线程安全的线程池的do_allocate当中:
在这里插入图片描述

如果说传入_Bytges大于largest_required_pool_block的话,就调用大的空间的分配方法:
在这里插入图片描述

在这里插入图片描述

可以看到它是拿到基类的对象,然后调用基类的分配内存的方法(_Resource->allocate)。

在这里插入图片描述

在这里插入图片描述

创建内存之后,把这块内存加入到_Chunks,也就是大块的数据空间当中。

所以这是我们的一个申请空间的流程,其中我们就理解了它的机制:
如果是第一次进来,那肯定是直接在内存池当中找到一块空的空间,然后直接返回;
如果多次进来之后,中途有一些内存被释放了,它其实会到里面去找,找到一个被释放的,并且跟它的大小是符合的,比方说它需要1M,那有一个1.5M的数据,那就可以直接给它,因为你不太可能找到完全一样的。

如果说你设定的max_blocks_per_chunk是100M,那这个时候到了1G的时候,就给它翻倍,翻倍到2G,哪怕你这次只申请了1M的空间,变成1G+1M,它会预先把内存池翻一倍,就变成了2G,如果是个32位程序的话,一下就把资源耗尽了,这样就导致空间的浪费,如果要充分使用内存池,那你还是要预先把这个空间准备好,或者干脆搞两个内存池,你确定下一个池给它1G,这是根据你的业务逻辑来确定怎么做。

3、c++17内存池空间释放代码分析

在这里插入图片描述

在这里插入图片描述

从同步内存池里面看内存释放的源码:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以看到,我们资源释放的时候并没有真正的释放,那么在什么时候会去真正的释放呢?
在这里插入图片描述

如果空闲的空间小于容器的空间,这个时候它是不释放的;
只有当它的空间减少到足够程度的时候才真正的释放,所以内存池也是减少了我们的一个释放的过程。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

看右图的变化情况,当申请内存到1G完成之后,开始释放内存:
在这里插入图片描述

从右图空间释放的图形变化来看,可以看到它是一块一块空间进行释放的(图形下降的坡度会缓一点),直到最后我们把所有的空间都释放完毕;
但是这时候它其实还保留了一部分空间的,在我们的整个数据块当中它并没有完全清理掉,如果我们想完全清理掉的话:
在这里插入图片描述

我们在任务管理器中实际看一下内存的使用情况:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

相关内容

热门资讯

Python|位运算|数组|动... 目录 1、只出现一次的数字(位运算,数组) 示例 选项代...
张岱的人物生平 张岱的人物生平张岱(414年-484年),字景山,吴郡吴县(今江苏苏州)人。南朝齐大臣。祖父张敞,东...
西游西后传演员女人物 西游西后传演员女人物西游西后传演员女人物 孙悟空 六小龄童 唐僧 徐少华 ...
名人故事中贾岛作诗内容简介 名人故事中贾岛作诗内容简介有一次,贾岛骑驴闯了官道.他正琢磨着一句诗,名叫《题李凝幽居》全诗如下:闲...
和男朋友一起优秀的文案? 和男朋友一起优秀的文案?1.希望是惟一所有的人都共同享有的好处;一无所有的人,仍拥有希望。2.生活,...
戴玉手镯的好处 戴玉手镯好还是... 戴玉手镯的好处 戴玉手镯好还是碧玺好 女人戴玉?戴玉好还是碧玺好点佩戴手镯,以和田玉手镯为佳!相嫌滑...
依然什么意思? 依然什么意思?依然(汉语词语)依然,汉语词汇。拼音:yī    rán基本解释:副词,指照往常、依旧...
高尔基的散文诗 高尔基的散文诗《海燕》、《大学》、《母亲》、《童年》这些都是比较出名的一些代表作。
心在飞扬作者简介 心在飞扬作者简介心在飞扬作者简介如下。根据相关公开资料查询,心在飞扬是一位优秀的小说作者,他的小说作...
卡什坦卡的故事赏析? 卡什坦卡的故事赏析?讲了一只小狗的故事, 我也是近来才读到这篇小说. 作家对动物的拟人描写真是惟妙...
林绍涛为简艾拿绿豆糕是哪一集 林绍涛为简艾拿绿豆糕是哪一集第三十二集。 贾宽认为是阎帅间接导致刘映霞住了院,第二天上班,他按捺不...
小爱同学是女生吗小安同学什么意... 小爱同学是女生吗小安同学什么意思 小爱同学,小安同学说你是女生。小安是男的。
内分泌失调导致脸上长斑,怎么调... 内分泌失调导致脸上长斑,怎么调理内分泌失调导致脸上长斑,怎么调理先调理内分泌,去看中医吧,另外用好的...
《魔幻仙境》刺客,骑士人物属性... 《魔幻仙境》刺客,骑士人物属性加点魔幻仙境骑士2功1体质
很喜欢她,该怎么办? 很喜欢她,该怎么办?太冷静了!! 太理智了!爱情是需要冲劲的~不要考虑着考虑那~否则缘...
言情小说作家 言情小说作家我比较喜欢匪我思存的,很虐,很悲,还有梅子黄时雨,笙离,叶萱,还有安宁的《温暖的玄》 小...
两个以名人的名字命名的风景名胜... 两个以名人的名字命名的风景名胜?快太白楼,李白。尚志公园,赵尚志。
幼儿教育的代表人物及其著作 幼儿教育的代表人物及其著作卡尔威特的《卡尔威特的教育》,小卡尔威特,他儿子成了天才后写的《小卡尔威特...
海贼王中为什么说路飞打凯多靠霸... 海贼王中为什么说路飞打凯多靠霸气升级?凯多是靠霸气升级吗?因为之前刚到时确实打不过人家因为路飞的实力...
运气不好拜财神有用吗运气不好拜... 运气不好拜财神有用吗运气不好拜财神有没有用1、运气不好拜财神有用。2、拜财神上香前先点蜡烛,照亮人神...