侯捷C++内存分配课程总结四:std::alloc行为剖析

剖析标准的容器分配器的动作
文章内容参照于侯捷 C++内存分配系列教程

文章目录

  • 侯捷C++内存分配课程总结四:std::alloc行为剖析
  • 一、std::alloc的动作概述
  • 二、std::alloc的具体行为分析
    • 1.malloc分配的内存块
    • 2.std::alloc运行过程
    • 3、RoundUp的计算
  • 问题分析

一、std::alloc的动作概述

在前边的文章中我们已经了解,一般常见的内存池是为一个类维护一个内存池,说是一个类,其实它的限制并没有那么的严谨,应该说是为了一种大小的子空间维护一个内存池,也就是说,只要每次分配的内存空间的大小相同(如容器),那么就可以使用同一个内存池为其分配内存。

std::alloc就是使用了这种思想,他并不是分散的维护一个一个的小的内存池,而是维护一个内存池的链表(长度为16),间隔8byte的维护16种不同大小的内存池。如下图所示:
图片来自侯捷C++内存分配课程讲义

#0所对应的节点连接的链表负责分配大小为8byte的子空间
#3所对应的节点连接的链表负责分配大小为32byte的子空间
以此以8byte的间隔向后类推
#15所对应的节点连接的链表负责分配大小为128byte的子空间

当需要一次性分配的内存超过了128byte,std::alloc()本身就不会为其服务,而会将这个需求转给其他函数去处理,这里我们会在后边剖析源码的时候去介绍

可是用户申请分配的空间可能并不会正好是8的倍数(通常都不是),这时,就会把他提升至最近的长度,并为其分配对应大小的内存块。

同时,在每一次需要调用malloc去分配内存时,std::alloc通常会分配比指定的大小更大的内存(至少为要求的40倍),其中一半的作为当前的内存块进行分割并交付,剩下的将会被储存至pool。

而当我们归还内存时,也是将他连接到对应大小的内存队列头部,如果超过了128byte,也会被转交给其他函数。


二、std::alloc的具体行为分析

1.malloc分配的内存块

在正式开始介绍std::alloc的行为时,我们需要首先知道malloc分配的内存块的具体格式,如下表:
图片来自侯捷C++内存分配课程讲义

  • 中间蓝色的block size是我们所需要分配的内存空间,获得的指针也指向这一块内存的首地址
  • block size上下两个紧邻的debug tail是用来分割可用内存和不可用内存的,通常被填充为0xfd,编辑器在回收内存的时候,也可以通过判断这一块内存是否被修改进而判断内存的行为是否正常。
  • 再向上的debug header区我会在以后对malloc的深入讨论时再去分析。
  • 下边的pad是为了将整个内存块调整至16的边界
  • 最上边和最下边的cookie用来记录整个内存的大小。如果你足够细心,你会发现,这块内存的大小应该是0x40(因为被调整了边界),但是cookie记录的却是0x41。这是因为cookie会借助整个数据的最后一个bit去表示这块内存是否已经被分配了,0表示未分配,1表示已分配。

将一块内存分配出去的标志,就是将上下cookie的最后一位置为1。


2.std::alloc运行过程

图片来自侯捷C++内存分配课程讲义

最初的时候,std::alloc()会创建一个链表(代码中的free_list),并将初始值都设置为0,以后将用他们去维护16个子块大小不同的内存池(间隔为8byte)。


图片来自侯捷C++内存分配课程讲义

当我们第一次申请分配内存时,由于pool为空,所以会调用malloc去分配内存。这里假设需求的内存是32byte,那么就会调用malloc申请为pool分配32 * 20 * 2 + RoundUp = 1280的内存(RoundUp的大小会在最后指出怎么计算),然后将其中的32 * 20 的内存分割成20块,连接在#3上,并将其中的第一块交付。

此时pool中还剩余有640的内存


图片来自侯捷C++内存分配课程讲义


此时,我们再次申请分配64byte的内存,这时由于pool中有内存,且大于所需分配的内存,那么就在pool中切割出尽量多的子块(大小为需要的64byte),然后将第一块交付,将剩下的挂载在#7的队列上。

此时pool的剩余内存为0,下次在需要分配内存时,又会需要调用malloc去进行分配


图片来自侯捷C++内存分配课程讲义

下边两张图片的行为和上面的行为相同,这里我就不做过多的解释了。

这里由于pool为空,且所需要分配的内存最近的队列上没有挂内存块,所以调用malloc分配新的内存并进行分割、交付、挂载。
这里也和上边一样,从pool中尽量多的切割出所需大小的内存,之后交付、挂载


图片来自侯捷C++内存分配课程讲义

在这里,连续3次申请分配88byte的内存,由于大小对应于#10链表的88byte,同时上边已经在#10的链表下边挂载了内存,所以这里直接取出内存块进行了交付,并调整#10链表的头指针。

此时,pool中剩余内存的大小为240byte


图片来自侯捷C++内存分配课程讲义


这一步再次申请分配8byte大小的内存,同样切割pool,交付,连接。
结束之后,pool中剩余内存为80byte。


图片来自侯捷C++内存分配课程讲义

下边这一步较为重要!!!!!!!!!


这里再次申请了104byte的内存,但是pool中剩余的大小为80byte,不足以为其划分,于是者80byte的内存就被当成了内存碎片,由于其大小对应于#9链表,所以将他挂载到了#9链表下,然后再次调用malloc重新分配pool。


图片来自侯捷C++内存分配课程讲义

后边两张图片的动作就是对前边的重复了,我将图片放出来就不做过多的解释了

这里结束时,pool中的剩余内存容量为24


图片来自侯捷C++内存分配课程讲义

至此,我们假设内存中的可用空间已经全部被分配了,也就是说再次调用malloc会失败,但是我们再次申请一个链表中尚未挂载的内存块

这里,我们申请72byte大小的空间,但是#8上边并未挂在内存块,pool中的大小为24也不足以分配一个空间,malloc在我们的假设中也调用失败。
于是编辑器就将pool中原有的24byte视为内存碎片,挂载到#2中。之后从#8链表开始向后寻找最近的挂在有内存块的链表,在这里是#9,取出其中的第一块,将其视为pool进行分割、交付、挂载。


但是,如果此时要求的内存大小后边没有挂载内存块的链表,如申请120byte,此时需要#14为其提供服务,但是#14和它之后的#15都没有挂载空间链表,此时alloc就会返回失败。


3、RoundUp的计算

在上边每次调用malloc的时候,都会额外分配一个追加量RoundUp
RoundUp = 已经分配过的内存大小的总和 / 16;


问题分析

上边的流程结束之后,存在有很明显的问题:

首先,当他判定没有内存可分配从而结束程序时,真的没有可用的内存了吗?
不,正如我们很轻易可以想到,至少又两块的内存是可以分配的:

1、在alloc所掌握的内存中,还有很多的内存块没有被使用,只不过这些内存块的大小都小于所需要分配的内存,如果将他们拼接起来说不定足够为这次请求分配空间。

那为什么不这么做呢?答案很简单,代价太高。由于在这个架构里,多次分配、归还之后,每个链表上连接的空内存块可能并不是在物理上相邻的,但是我们要写数据需要的是物理上相连的内存,但是我们在这些内存块中去搜索物理相连的内存的难度又太大,所以并没有这么做。

2、由于每次调用malloc都会分配很多的内存,但是我们所真正需要的仅仅是其中的一小部分,那可以减少malloc所要求的内存,这样很多情况下都可以获得一块足够大的内存。

这个架构同样没有这么做,其原因在于,处于多进程的运行环境下,OS可能不仅仅跑了当前这一个程序,如果采用2的办法,我们虽然可以获取内存,但是我们也很可能完全占用OS的全部内存,那么其他的运行程序就会崩溃。ps:我个人理解中,RoundUp之所以调整的这么大,有可能与这一点也有关

其次,回收的内存并没有还给OS
正如之前的文章中描述的那样,将如此琐碎的内存还给OS是一件非常困难的事情,所以alloc中也并没有这样做。

C++内存分配详解四:std::alloc行为剖析相关推荐

  1. spark on yarn 内存分配详解

    spark on yarn 内存分配详解

  2. 【C++】动态内存分配详解(new/new[]和delete/delete[])

    原文链接:https://blog.csdn.net/qq_40416052/article/details/82493916 代码还是原文看着方便,在此不调整格式了 一.为什么需要动态内存分配? 在 ...

  3. 【C语言】动态内存分配详解

    目录 一.为什么有动态内存分配 二.动态内存分配函数 (1)malloc()函数 (2)calloc()函数 (3)realloc()函数 三.常见的动态内存错误 1.越界访问 2.内存泄漏 3.对N ...

  4. C语言动态内存分配详解

    文章目录 前言 一.为什么存在动态内存分配 1.已掌握的内存开辟方式 2.上述开辟空间方式的特点 3.为什么存在动态内存分配 二.动态内存函数的介绍 1.malloc 2.free 3.calloc ...

  5. [转载] JAVA 堆栈 堆 方法区 静态区 final static 内存分配 详解

    参考链接: 在Java中为静态最终static final变量分配值 转载来源:https://blog.csdn.net/peterwin1987/article/details/7571808 J ...

  6. 指针不显示 upupw_Go高级编程:指针和内存分配详解

    点击上方蓝色"Go语言中文网"关注我们,设个星标,每天学习 Go 语言 定义 了解指针之前,先讲一下什么是变量. 每当我们编写任何程序时,我们都需要在内存中存储一些数据/信息.数据 ...

  7. lwbt的内存分配详解

           Lwbt的mem分配是通过一个大的字节数组memp_memory来派发的.在这个数组中规定了哪一段是属于哪个类型的,这样做的方法不是很科学,是通过规定各个类型结构的最大能用的个数来取的. ...

  8. 内存分配详解 malloc, new, HeapAlloc, VirtualAlloc,GlobalAlloc

    很多地方都会使用内存,内存使用过程中操作不当就容易崩溃,无法运行程序,上网Google学习一下,了解整理下他们之间的区别以及使用 ,获益匪浅 0x01 各自的定义和理解 (1)先看GlobalAllo ...

  9. 计算机操作系统执行可执行程序时,内存分配详解

    处理器遇到的地址都是虚拟地址,虚拟地址和物理地址都分成页码(页框)和偏移量俩部分组成.在虚拟地址转换成物理地址的过程中,偏移值不变,而页码和页框码之间的映射就在一个映射记录表--页表中 当进程创建时, ...

最新文章

  1. SQL SERVER 跨服务器查询
  2. java 服务器长链接_Java如何实现长连接
  3. 《SpringBoot揭秘 快速构建微服务体系》读后感(三)
  4. 三联《少年》创刊,各领域佼佼者畅言新知,帮少年建立思维素养体系!
  5. python播放音乐同步歌词_使用Python下载歌词并嵌入歌曲文件中的实现代码
  6. php issign为false,支付宝接口集成及错误排除
  7. @echo off是什么意思_高街、BF、FOG、OS风。。。都是些什么鬼?
  8. python get sheet_Python模块学习 - openpyxl
  9. matlab虚拟现实之工具介绍(修改)
  10. 微信全球MBA创新大赛Roadshow最终站火爆中欧
  11. 超级实况服务器维护中,超级实况最新版攻略
  12. 网页设计html流水效果图,15例简单常用网页设计效果代码
  13. AMD发布22.9.2驱动,支持《禁闭求生(Grounded)》
  14. BUUCTF-[QCTF2018]X-man-Keyword
  15. 前端如何处理十万级别的大量数据
  16. 2022-2028全球汽车轮胎充气泵行业调研及趋势分析报告
  17. QQ玩一玩(轻游戏)开发环境搭建与调试
  18. 苹果电脑win10蓝牙音响卡顿_win10蓝牙音响音质不清怎么解决
  19. 微信小程序 滚动列表(无限滚动)
  20. Java判断一个数字是否是素数

热门文章

  1. android卡为什么iOS不卡,同内存下苹果不卡,安卓却很卡,原因在这里
  2. 华为q1设置虚拟服务器,华为路由器Q1手机设置教程
  3. HTML5期末大作业--主题绿色环保生态城市规划环境保护公益主题-环保垃圾分类
  4. 用计算机弹惊雷怎么弹,《惊雷》《飞鸟和蝉》20分钟教你弹!
  5. margin相对的元素
  6. 花多多拼团系统开发(功能规则)
  7. mac连不上热点和网络
  8. 总结人脸识别的方向(FD,FA,FR,FV)
  9. HTML缩写元素: <abbr>-超文本标记语言| MDN
  10. ue4如何链接html5设备,UE4怎么对接Steam