C++ 内存管理(三)----VC6内存分配

一、源码

代码执行次序是从下往上的,且可知在进入main程序执行之前,先执行的是mainCRTStartup的前置程序(分配内存空间)。
在这里插入图片描述

- _heap_init() 和 _sbh_heap_init()

在这里插入图片描述_heap_init()内部代码操作为:先通过操作系统函数HeapCreate()向系统要一块heap空间并命名为 _crtheap,然后再进入_sbh_heap_init()内部调用操作系统函数HeapAlloc()在 _crtheap的空间上进行操作分配出16个HEADER。

HEADER结构:
每一个HEADER(绿色)都可以是由下面灰色的一段结构组成,
在这里插入图片描述

由HEADER数据结构知,其有两根指针,且有三个unsigned int型变量则其大小为4字节(即32bits),bitvCommit为上面那行的32bits,bitvEntryHi和bitvEntryLo则合起来为下面那行的64bits。

- _ioinit()

在这里插入图片描述进入该代码段,提出要求分配大小为256的空间即32 * 8【ioinfo的大小为6,对齐调整为8】。

- _heap_alloc_dbg()

在这里插入图片描述
这里的nSize为传入参数即为前面要求分配的空间大小256byte【16进制下则为100h】,nNoMansLandSize宏定义大小为4。

而结构体_CrtMenBlockHeader (这块是附加上去给debuger时用的东西) 的大小如右上图所示:
前面两个是指针类型变量;
第三、四个是让调试器知道,这是从哪个文件的哪一行发出的请求【在ioint.c文件中的81行发出的请求】;
第五个记录真正需求放置数据的空间大小【100h】;
第八个是字符形成的数组,实际大小为4;

所以最终设计下来,真正的100h数据是被夹于如图两个gap之间的【为了防止放入数据时,出现大小越界或恶意修改的情况。因而用两个gap去保护它】,并且前面带有一些用于调试的数据信息。其实该结构的头尾段还需加入cookie,则最终实际大小为
32 + 100h + 4 + 2 * 4
=0x20h + 100h + 0x04h + 0x08h
=12Ch

则由上知,经过_heap_alloc_dbg()是为了在原来基础上加入debuger的信息。
而下面的操作则是说明,要在里面具体填入何种类型的东西以及该填入东西的意义:

在这里插入图片描述
以上均没有进入到实际内存的分配操作。

- _heap_alloc_base()

在知道所需要附加上的debuger的总的需求大小后【注意此时仍没有进行内存空间的分配,只是在发出需求】,进入_heap_alloc_base()进行判断,如下:
在这里插入图片描述
这里的 if 判断条件里面 __sbh_threshold3F8(十六进制数)即实际大小为1016【实际是1024除去头尾连个cookie大小为8所得,1016=1027-2*4】,而经过前面的添加包装传入的size (没加cookie的) 大小为256+32+4=292小于1016【即小于1K】的,所以进入__sbh_alloc_block()操作:
在这里插入图片描述这里的操作是为前面传进来的经过debuger信息包装的数据再添加上头尾两个cookie,然后进行round up即调整到16的倍数关系【当添上头尾cookie后得到的大小不为16的倍数时,则round up成16的倍数大小】。
如这里的最终大小为0x130,所以cookie记录的信息是0x130。但这里为什么是0x131呢,因为这块内存是要分配出去的【而再最终分配内存都是16的倍数大小即十六进制的末位必为0】,所以通过采取末位bit01的方式表示生成的这块内存到底是分配出去使用了【1】还是已被sbh回收未被使用【0】。

进行到这里,都还是在计算所需分配的内存的大小,仍没有真正地开始实际的分配内存的操作!!

接下来进入到 _sbh_alloc_new_region() 即开始进入实际分配内存前置操作
需要构建一个region控制台,仍未开始实际分配内存】:

在这里插入图片描述由上知,有16个HEADER,而HEADER中的两个指针如图,一个指向region,一个指向1MB的系统虚拟地址空间【调用_virtual_alloc】。

region结构如图
在这里插入图片描述在这里插入图片描述

这里的struct tagRegion里面:
第一个表示当前是哪个Groupx在被调用;
第二个表示最上面的深灰阴影的那一行;
第三、四个合起来表示有32组每组有64bits来记录对应32组Groupx的各自64对指针的是否存在即可使用状态;
第五个为struct tagGroup类数组,表示32个Groupx【Group0,Group1,Group2。。。Group31】。

struct tagGroup里面:
第一个表示当前进行了分配或释放空间的次数【正数则代表分配操作,负数则代表释放回收操作,0则表示系统已经将分配的空间全部回收或没有任何空间被分配使用】;
第二个为struct tagListHead类数组,表示有64个struct tagListHead类对象【由下知,实际为64对指针】。

struct tagListHead里面:
有两个tagEntry类型的指针对象【即为上面Group表中的那64对指针】。

struct tagEntry里面:
第一个表示指针借用前面的一部分而指向包括自己在内的三位【因为在后面控制8page时,需要设计成指向包括记录大小的头cookie和前后指针的三块内容;所以实际sizeFronnt=3,即从所指向的地址开始向下包括3块内容;另外因为设计成这样的功能,所以在GroupX中的64对某对链表指针没被投入使用时,一般都是往上指向前一个pEntryprev指针的地址从而向下包含3个的】。如下图所示。
第二个则表示类似链表的双向指针。

在这里插入图片描述在这里插入图片描述

而上面region的这些东西的大小为4+64+64 * 4 +32 * (4 + 64 * 8) = 16836 byte,
而16836 / 1024 = 16.33KB,即需要创建一个region消耗16KB的空间去管理系统分配出来的1MB的空间。

接下来进入到 _sbh_alloc_new_groupn() 即开始进入实际分配内存操作:
在这里插入图片描述这里开始将系统提供的1MB内存空间,实际划分为32份分别归属于32个Group,则每一份大小为1024/32=32KB。然后又将归属于各个GroupX的32KB划分为8个page,即每一份page大小为32/8=4KB【4096byte】。
在这里插入图片描述

而对于每个page,其有两个防护cooike【0xffffffff】,防止填充或合并内容时出现越界而修改其结构出错。4096 - 2 * 4 = 4088,而填充的内容又必须调整为16的倍数,所以调整为4080byte,然后剩余的8byte作为无用的保留内容。

另外这有点像前面第二讲所说的,即先取出一大块内存空间,然后做切割再把它串接起来,然后挂在对应链表指针上。而在这里一开始是先实际分配了32KB的系统内存【1MB只是相当于一个门牌号标识作用,实际只是产生分配了32KB去使用】,然后是挂在Group0下的最后一对即[63]指针上的。一直等到将Group0上的32KB分配完了再出现从(1MB - 32KB)的系统内存空间再要一块32KB的然后归于Group1管控安排。

而且,这里虽然也像第二讲那样,将64对指针划分成各自负责的编号,即:
[0]对指针负责大小为16byte的内存;
[1]对指针负责大小为32byte的内存;
[2]对指针负责大小为48byte的内存;
。。。
[63]对指针此时 应负责大小为1024byte的内存但实际由图知,[63]此时负责的是8*4096byte=32KB大小的内存】,且大于1KB即1024byte的都由最后这对指针负责,只有当切分到的内存空间小于1KB时才对应看应归属于前面的哪条链表【但此时也是有条件的,即使此时计算得出其归属于何种链表时,还应看此时该链表是否是可用状态。若可使用状态对应状态标识量为0(即那记录对应32组Groupx的各自64对指针的是否存在的64bits的那一长串链表),则会强行归属于[63]对指针管控分配不属于计算所得应归属于对应[x]的指针管控!!】。

使用者ioinit申请分配空间时的切分空间操作
在这里插入图片描述
ioinit申请的时256byte的大小空间即小于1KB的【16进制下为100h,经过包装后12Ch向上调整为130h】且经计算应由[18]负责进行分配【130h/10h=13h -> 16+3-1=18】,但由于一开始[18]的状态标识量为0即该链表下的内存空间为空,所以一开始其是从[63]对指针负责的page1开始分配的

在这里插入图片描述

4080在16进制下为ff0,此时需要分配130大小的空间出去,则还剩下ec0大小的内存空间,并将剩余的内存空间ec0挂回[63]那对指针负责下次分配。而此时,0x00000131这块地址已经返回给申请者ioinit使用所以0x00000130->0x00000131表示该内存空间已被分配使用。

二、SBH行为分析—分配+释放图解

在这里插入图片描述

HeapAlloc( , , 16 * sizeof(HEADER));
在堆里创建出16个HEADER,从而进行管控分配对应内存的操作。

HeapAlloc(_crtheap, , sizeof(REGION));
在堆里的指定内存名为_crtheap的内存空间创建HEADER的管控台region。

p = VirtualAlloc(0, 1Mb, MEM_RESERVE, …);
传入参数0表示在随意位置向操作系统发出请求,要虚创建HEADER的虚拟内存空间地址,其大小为1Mb,然后以MEM_RESERVE即保留地址名的形式创建,返回指针地址。
【非真正创建实体的内存空间,而只是挂上虚拟的地址空间门牌号。等到真正要用时该虚拟内存空间才存在!!】

VirtualAlloc(addr, 32Kb, MEM_COMMIT, …);
在前面已经标出门牌号的内存地址addr处,实质创建HEADER的32Kb的内存空间地址,然后以MEM_COMMIT即真正的分配来给内存空间!
【非真正创建实体的内存空间,而只是挂上虚拟的地址空间门牌号。等到真正要用时该虚拟内存空间才存在!!】

首次分配,是申请分配100h调整后为130h的内存空间,而130h/10h=13h -> 16+3-1=18则应该由#[18]对指针去负责分配。但由 bivGroupHi[0]和bivGroupLo[0]组成 的表示Group0对应编号对指针的可用状态标识位知,此时只有[63]指针下挂有链表内存可被分配使用。所以由Group0下的[63]指针下的page1进行内存分配。分配完空间后,剩余eco的内存重新挂回[63]指针的pnext以及串回page2的统计大小的4080的指向地址。
然后分配出去的130大小的内存空间,因为使用的是嵌入式指针,分配出去后,使用者在向分配所得的空间填入内容时可能将嵌入式指针覆盖掉【但无所谓,在回收时重新用指针指回就可】。
注意此时分配或释放次数为1。

在这里插入图片描述
第二次分配,是申请分配调整后为240h的内存空间,而240h/10h=24h -> 2 * 16+4-1=35则应该由#[35]对指针去负责分配。但由 bivGroupHi[0]和bivGroupLo[0]组成 的表示Group0对应编号对指针的可用状态标识位知,此时只有[63]指针下挂有链表内存可被分配使用。所以接着在前面分配剩余的ec0空间上继续分配,操作同上。分配完空间后,剩余c80的内存重新挂回[63]指针的pnext以及串回page2的统计大小的4080的指向地址。
注意此时分配或释放次数为2。

在这里插入图片描述
第三次分配,是申请分配调整后为70h的内存空间,而70h/10h=7h -> 7 -1=6则应该由#[6]对指针去负责分配。但由 bivGroupHi[0]和bivGroupLo[0]组成的表示Group0对应编号对指针的可用状态标识位知,此时只有[63]指针下挂有链表内存可被分配使用。所以接着在前面分配剩余的c80空间上继续分配,操作同上。分配完空间后,剩余c10的内存重新挂回[63]指针的pnext以及串回page2的统计大小的4080的指向地址。
注意此时分配或释放次数为3。

。。。

在这里插入图片描述
第十五次时是进行回收操作,回收240h的内存空间,而240h/10h=24h -> 2 * 16 + 4 - 1 = 35则应该由#[35]对指针去负责回收。回收后,将嵌入式指针恢复指向即可。而此时#[35]下则已挂有链表内存,则bivGroupHi[0]和bivGroupLo[0]组成的表示Group0#[35]编号对指针的可用状态标识位变为1。则下次申请的内存经计算是由#[35]负责分配时,则可直接进入#[35]内进行分配操作。又或下次申请的内存经计算是由如#[24]或#[29]等比#[35]小的指针且其仍为空状态负责分配时,此时采取就近分配原则由#[35]指针下挂的链表内存进行分配操作。

在这里插入图片描述
注意:此第16次的分配动作为申请分配b0的内存空间,由15步知,此时有#[35]和#[63]可负责分配内存操作。而b0h/10h=bh->11-1=10,则其应由#[10]负责分配,但因为#[10]下链表内存为空,所以就近原则由#[35]负责分配。由上知,#[35]下链表有240大小的内存空间,所以分配完后剩下240-b0=190的剩余内存空间。而190的剩余内存空间经计算190h/10h=19->16+9-1=24,即应归还调整至#[24]指针下负责。
此时#[35]下本来负责的240内存空间,分配b0出去给申请者后,剩余的190内存空间又被调整至#[24]指针下,所以此时#[35]的可用状态为0即其下链表内存空间为空。而#[24]由于其下挂有190的内存空间所以可用状态为1。因而最终bivGroupHi[0]和bivGroupLo[0]组成的表示Group0对应编号对指针的可用状态标识位0000008000000001【8为1000】。

。。。

在这里插入图片描述在第n次分配时,需要申请230大小的内存空间。而由Group0的bivGroupHi[0]和bivGroupLo[0]组成的表示Group0对应编号对指针的可用状态标识位知,此时只有#[6], #[27], #[29]为可使用状态【#[63]也用完了已经】,而230h/10h=23h->2 * 16 + 3 - 1 = 34即由#[34]负责,但#[34]其本身为空且其右边临近的#[35]…也全为空,而其左边的#[6], #[27], #[29]虽为可使用状态,但内存空间明显不能满足230大小要求。所以只能向Group1开始发出请求!!而且进入到Group1进行管控分配内存时,indGroupUse变为1,表示当前是由Group1进行分配内存空间的操作。

合并操作:
在这里插入图片描述
先向下合并:弓箭所指位置往上移动4byte,即移动到0x00000301的地址处,然后知道其长度大小为300,所以进一步移动300的距离,则此时已经移动到下一个空内存块0x00000300的地址【且此时监测到其末位bit为0,则表示是空区域可以合并】,所以合并为中间图的600大小的内存空间。

再向上合并:此时指针需往上移动两次4byte即8byte【因为向上移动一次4byte知能到达指向?的那块内存地址,此时则需要再往上移动4byte便到达上面的0x00000300的地址,且可知道上面的内存块也未被使用和其长度为300。所以往上再移动300大小的长度,则移动到上面那块未被使用内存的头端cookie,因而进行合并最终变为内存大小为900,cookie为0x00000900】。因而在向上合并时,下cookie的设计十分重要,否则无法向上合并空内存块

最终计算900大小的内存空间,900h/10h=90h->9 * 16 - 1 = 143(超过64了),应回收至Groupx中的#[63]的指针下。

三、分配内存归还操作系统

  • 归还回SBH
    在这里插入图片描述

在调用 free( p),将分配出去的内存回收时【只是回收到SBH中对应链表上,仍没有直接归还操作系统】:
1.首先通过__sbh_pHeaderList逐个进入到对应Header里面,通过计算其Group的头尾的范围,看p是否落在其范围内,一直到找到对应范围则知道p是属于哪个Header的了;
2.将p指针与当前所属的Group的头端指针相减然后除以32再减1,则可知道其对应落于Groupx内;
3.进入到对应的Groupx内后,将p指针往上移4byte则可得到其内存大小,然后将所得内存大小除以10h再转为10进制数,然后再减1则可得应归属于#[x]的指针链表上。
由此,p便被SBH回收。

在这里插入图片描述
这里采用将1MB内存切分成每32KB的小块分段管理是为了回收时机方便,若只是一整块,则回收时需等整块1MB的内存空间全部释放完了才能进行回收;而分段管理时,只要各分段的32KB的内存被释放完了就可以回收给系统,回收周期时间短。
另外这里通过struct Group中的int型成员变量cntEntries来判断当前Group是否全回收。

在这里插入图片描述
defering为延缓全回收时将内存释放回操作系统的动作:
因为如果Group一全回收就马上释放回操作系统,那如果代码下一步又需要分配内存空间时则又需重新进行Group的创建等工作,这将会降低了代码执行的效率。
所以引入了__sbh_pHeaderDefer来指向一个全回收Group所属的Header,而__sbh_indGroupDefer则指向当前Header中的Region中的全回收Groupx

在这里插入图片描述
所以当所有内存块均被归还SBH时,SBH回到一开始的模样。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值