侯捷 - C++ Startup 揭密:C++ 程序的生前和死后 (二)

2.内存分配

先膨胀大小决定要多大内存,然后去拿内存,拿完后函数返回,然后设置区块串接。

2.1 计算分配的大小并设定值

malloc_crt:

  • 调试模式——使用一般的malloc分配内存
  • 非调试模式——malloc_dbg分配内存并登记一些信息(所在的FILE,LINE)。

因此,任何一个C++程序第一次分配的内存大小都是 IOINFO_ARRAY_ELTS * sizeof(ioinfo) = 256 。它用来作为_cdecl_ioinit()函数,即IO初始化。

在调试模式下,要加上上下文等相关信息,加完的长度要登记在cookie之中(图中的00000131,在之前的130的基础上需要1个bit登记状态),最后要进行字节对齐。

100h(转化位10进制:256)+ 24h(36) + 8h(8)  = 12ch -> 130h (16的边界)。上图字节补齐的4个字节没有画出。

我们来看heap_alloc_dbg函数:

heap的客户(需要被分配内存的地方)可能是CRT本身 ,可能应用程序。在要完内存后,若小于1K,该区块将被膨胀(上文提出),膨胀完后会去SBH挖取一块内存(后文将介绍),然后区块将被串接起来(如下图Debug Heap 双向链表)。

heap_alloc_bae:内存门槛检查,小于1K将从SBH拿取内存,大于将从操作系统的内存池去拿(下图不小心备注错了)

侯捷 - C++ Startup 揭密:C++ 程序的生前和死后 (二)_第1张图片

 

2.2 去SBH拿取内存

在32位的电脑没有64位变量可以用,bitvGroup被分为了Hi和Lo两部分,BITVEC是unsigned int型,为32位。所以,共有[32]组,每组64bits。

作为heap的管理者,怎么去管heap的区块呢?我们都是挖一大块内存,有需要的时候切出去,当我回收的时候它们的大小是相同的:比如我给ABC3个人,它们需要不同大小的内存,它们还给我的时候,如果还的大小是一样的,我就想办法把它管理在一起,而不要散落在原始的物理的那一部分,例如我们可以使用特殊的设计将它串在一起(如上图,将内存串接起来)。

回到上图,该设计准备了64条双向链表,负责管理64种大小。当回收的时候,有相同的大小,我们就把他链在特定的某个链表上(逻辑上是连续的,物理上却不是)。

侯捷 - C++ Startup 揭密:C++ 程序的生前和死后 (二)_第2张图片

1 MegaByte,拿出其中的1/32即32K(因为其不希望一次管理太多内存),在将32K切为8块,每块为4K。

侯捷 - C++ Startup 揭密:C++ 程序的生前和死后 (二)_第3张图片

将page放大来看:

当切出130bytes出去立马就变成了131bytes了,我们之前说过,因为它的这个设计需要1个bytes表示它的状态是给出去了还是在SBH手中。 

0xffffffff表示为-1(上下个占8个byte),用来防止链表合并。4096-8*2=4088。前面讲过希望设计大小是16的倍数,最靠近16边界的是4080,因此8bytes保留。

侯捷 - C++ Startup 揭密:C++ 程序的生前和死后 (二)_第4张图片

 

 总结:

首先,我们有16个header,每一个header管理的事情是1M里面的32K切成8块,这8块是由64条链表来管理。

一开始是最后一条链表负责把page8链接起来(130=304/16=19,从0开始排列,19-1=18。理论应该是#18 List进行供应,但因为现在这些链表都是空的,只有最后一个链表是有挂载page,所以第18号链表是空的,最后发现最后一个链表的page起着作用,所以在page中把130bytes切出去了)

在page中把130bytes切割出来。130bytest、是所有程序都会面临,_ioinit()需要的内存大小就是130bytes,于是剩下ec0bytes 。这就是首次内存分配。

接下来对page1继续切割(只有当page1 内存切割完成后,才会去切割page2的内存),当切割至第15次,开始释放。

链表的各自任务是以16在变化的,所以由#35 链表进行回收。#35 链表的指针将指向240bytes的地址。

这样回收由什么好处呢?下一次客户需要240bytes大小空间时,#35 链表将直接给它。

当第16次分配内存时,将分配b0大小。操作系统将从之前释放的240bytes中的空间重新切割。240-b0=190,剩下的190将重新分配给#24 List维护管理。所以,内存切割分配是个动态的过程,不断调整所属的链表。

区块的合并

侯捷 - C++ Startup 揭密:C++ 程序的生前和死后 (二)_第5张图片

合并后的空间也将重新分配给链表进行管理。

 

 

侯捷 - C++ Startup 揭密:C++ 程序的生前和死后 (二)_第6张图片

内存块将回到最初始的状态,理论上它可以在合并,但是应为我们前面说过,特意放上separate分隔的东西(0xffffffff),所以它不能继续合并。

 

参考:

博览网:侯捷 - C++ Startup 揭密:C++ 程序的生前和死后

 

 

 

你可能感兴趣的:(C/C++)