C++内存管理(候捷)第三讲 笔记

VC6和VC10的malloc比较

在这里插入图片描述
调用栈,从下往上
mainCRTStartup函数是CRT(C Run Time,C标准库)提供的入口点函数 ,调用一系列函数,后面才是调用main函数
_heap_alloc_base函数,当size小于一个阈值_sbh_thrshold时调用__sbh_alloc_block函数,否则调用HeapAlloc函数,后者是操作系统提供的内存分配函数
在这里插入图片描述
不再做门槛检测,无论多大内存,都交给操作系统申请,对于VC10版本,它的SHB等小块内存的管理都被包装到HeapAlloc里面来了。
在这里插入图片描述
_heap_init里面调用HeapCreate来分配一块大小为4096的堆空间,命名为_crtheap,后面CRT的动作都要从这一块内存中来拿
_heap_init里面调用__sbh_heap_init,里面时HeapAlloc,从_crtheap中拿内存,准备好16个header
所以 _heap_init的作用就是准备好16个header
在这里插入图片描述
其中的一个head结构,hi和lo组合共64位,下面一行,commit32位是上面一行

vc6内存分配

在这里插入图片描述
CRT进行的第一次内存分配,调用了_malloc_crt进行内存分配。分配的大小为32 x 8 = 256B,所有的程序一进来都是分配256B。256在十六进制下是0x100,或者写成100H
_malloc_dbg 是与调试相关的内存分配函数,它是 Microsoft Visual C++ 提供的一种扩展版本,用于在调试模式下进行内存分配,并提供额外的调试信息
在这里插入图片描述
之后调用_heap_alloc_dbg,里面右下角计算blockSize的时候,_CrtMemBlockHeader是一个结构体,可以称为debug header,nSize就是上文提到的256B,后面的nNoManLandSize是4
在这里插入图片描述
_pFirstBlock和_pLastBlock两根指针指向block链表的头尾。malloc分配的内存块都用链表串起来
调试模式下多了(右上图)深灰色之外的东西

上图右下角的memset是给特定地方填入特定的值
在这里插入图片描述
_heap_alloc_base分配内存,把刚刚扩充的大小在去比较,小于阈值的内存交给sbh服务,大于阈值的内存交给操作系统HeapAlloc来服务。
这里_Sbh_threshold的值是1016,因为还没有加cookie(大小为8),两者加起来是1024B
在这里插入图片描述
在前面block的大小(intSize)基础上,上下添加cookie(2*sizeof(int),以及右侧上下两块红色的地方0x131)。(BYTE_PER_PARA-1) 是进行向上调整ROUND_UP,调整到16的倍数。

cookie计算:_ioinit首次需要的内存256B(0x100,浅绿色),调试器加的debug header大小为 9 x 4 = 36B(灰色部分(0x24)和上下深绿色部分(2*4个0xfd)),再加上下两个cookie大小4 x 2 = 8B(0x8)。0x100 + 0x24 + 0x8 = 0x12C,向上调整到16的倍数。变成0x130,所以cookie应该填0x130。
图中显示的是0x131,因为调整为16的倍数之后,这个十六进制数的二进制表示后四位全是零,最后一位另作他用,如果最后一位是1表示这块内存分配出去,如果最后一位是0表示这块内存还在sbh手上。这里是已经分配出去的内存
在这里插入图片描述
前面的函数都是确定分配内存的大小

16个header,每一个header负责1MB的内存

_sbh_alloc_new_region:new一个管理中心
header有两个指针,一个指向真正的内存(上图最右边),为了管理内存,new一个管理中心region,(上图橙色框内容),另一个指针指过去
一个整数, 64个char。hi和lo的命名形式即为两者并起来,BITVEC是一个unsigned int。共有32组,每组64bits。
再之下是32个group,每一个group是64个ListHead,ListHead里面有两根指针,形成一个双向链表

一个region的大小大概有16K左右。即为了管理右侧的虚拟地址空间,它的成本是region(管理中心)的大小,16K
在这里插入图片描述
_sbh_alloc_new_group:从1MB内存中切一块

将右侧的虚拟内存空间(大小为1MB),分成32块。每一块大小为1MB / 32 = 32KB

每一块再细分为8个page,每个page大小为32KB / 8 = 4KB,如上图page1, page2, …, page8

虚拟内存先挖一块32KB,有group0管理,32KB分为8个page,每个page类似链表串连起来(上图下方)(实际是连续的,一次性向操作系统申请的),挂在group0里64个链表的最后一根

当_ioinit第一次来要内存的时候,就从group0的page1挖一块给它。后面又有要内存的时候,就一直往后挖,如果page1到page8都被分配出去了,之后还是要内存,就到group1中去处理

SBH向操作系统要内存的时候,一开始并不是1MB,而是一个块32KB
在这里插入图片描述
每一个page的偏移值地方设置为0xffffffff也就是-1,上图黄色部分,作用是合并的时候做分隔符(栅栏),分隔符(栅栏)之内的合并在一起。
第一个红色小块是记录可用空间的大小,这里是4080(由4KB = 4096B,4096减去两个黄色的部分(栅栏,分隔符)8B,剩下4088B,但是要下调到16的倍数,变成4080B,剩余的放到保留区)上图看起来是8块分开的,实际是连续的内存。上下两块4080是cookie,记录自己这一块的大小。剩下两个红块是两个指针,将8个page串起来

group中的listhead的指针就是指向page

有64条链表是为了负责不同大小的区块,分别是16B, 32B, 64B,…, 每次增加16B,一直到最后一根链表,最后一根应该负责64 x 16 = 1024B的区块分配。(参考之前第二讲)
另外最后一根链表还有一个任务,就是所有大于1024B的区块都由它负责,当切分完之后如果剩下的空间小于1024B,就要挂载到对应区块大小的那根链表上

64根链表上面还有一个整数cntEntries,表示分配的累积量,分配出去一个区块就+1,回收回来一个区块就-1.
在这里插入图片描述
_ioinit第一次要的内存是256B(0x110),然后加上各种debug header和其他,总共是0x130,所以给出去的内存是0x130,cookie记录的值是0x131

剩下的大小为0xff0(4080B) - 0x130 = 0xec0。 黄色下方第一块,记录的内存大小变为0xec0

红色的地址0x007d0ed0是传出去的指针,指向的是客户要的0x130大小(加上各种debug header等)的内存,上下两个cookie记录了大小。然后0x130内部还要调整指针,指向实际要的大小0x100大小的位置(上图中间绿色部分)

函数栈不断return过程中,不断调整指针大小,最终返回绿色部分

_NORMAL_BLOCK 和_CRT_BLOCK指的是不同类型的block,_NORMAL_BLOCK是main函数里面具体用的block,它在main函数结束的时候应该全部被归还,否则就是内存泄漏;而_CRT_BLOCK在main函数运行结束之后还会存在,它会由CRT进行释放

返还的时候,把130H除以16(每个链表间隔16),再减一,对应的链表指针即会拉过来,指向130H这块

SBH行为分析 分配+释放之连续动作图解

在这里插入图片描述
首次需求是由ioinit.c第81行代码发出,申请100H的空间,加上各种debug header,它的区块大小变成130H(十进制是304),应该由64条链表中的第304 / 16 - 1 = 18号链表进行供应

SBH面对这样的需求,在初始化的时候已经有16个header,现在0号header来进行处理,先分配1MB的地址空间,这个动作是由VirtualAlloc去拿到的

p = VirtualAlloc(0, 1MB, MEM_RESERVE, ...)

其中MEM_RESERVE 标志表示要保留这个地址空间,而不分配物理内存。

然后header0有另外一根指针分配出region,由HeapAlloc进行

HeapAlloc(_crtheap, sizeof(REGION));

第一个动作是由操作系统海量的内存去抓取1MB,第二个动作是由前面已经建立的_crtheap去拿,初始4096(当不足时会自动扩充)

region里有一些bit,和32个group,每个group有64条链表(64对指针)(上图右上部分)

之后,再从1MB中,真正的申请32KB,就是8page,每个page4KB
由group0来管理第一块32KB,即8个page间各以双向指针相互串起来,最后串回到64条链表指针的最后一个

即每个group管理一块32KB,一块用完,到下一个group,再去实际申请32KB,group里的64条链表的作用就是第二讲的内容

VirtualAlloc(addr, 32KB, MEM_COMMIT, ...) // MEM_COMMIT表示真的分配内存

最后,从page1上分配最开始的需求,申请的100h,区块大小130h,4080剩下的大小为0xff0 - 0x130 = 0xec0,在SBH控制之中,130h被分配出去,cookie记为131h
在这里插入图片描述
红色方框中是32组64bits,64bits分别对应64根链表的状态,哪一条链表有挂区块,对应的bit就设置为1。32组表示的是32个group

有32组group,因此需要32行,每一行是64位
在这里插入图片描述
第二需求分出240H的大小(包含各种debug header,调整16的边界等之后的大小)
240h = 576d(d表示十进制),576 / 16 -1 = 35,挂载到35号链表,然后检查35号对应的bit,看有无区块,此时链表是空的,因此遍历寻找更大容量的链表,最终找到最后一条

检查63链表里有8个page,page1还有空间,在此切出240H大小,page1还剩c80h大小
在这里插入图片描述
第三次分配70h的大小,找6号链表,为空,依次又找到最后一个链表
在这里插入图片描述
第15次的动作,14次分配后,第一次释放。cntEntries由14变成13,内存释放会-1.
释放大小为240h的区块,回收到6240h = 576D, 576 / 16 - 1 = 35号链表。分配出去的cookie为241h,现在将其变为240h,就表示回收回来。并且把240H后两个int,作为指针拉到35号链表,然后64bits中第35号bit需要由0变成1
在这里插入图片描述
第16次动作分配b0h,由b0h = 176D, 176 / 16 - 1 = 10链表服务,为空。往右查找,发现35号链表可用。
上次回收回来240h,分配出去b0h,还剩240h - b0h = 190h。移动到90h = 400D, 400 / 16 - 1= 24号链表。此时64bits中的24号bit需要变成1,表示该号链表有区块可分配
在这里插入图片描述
group1链表使用情况02000014 00000000H,展开二进制,有3个链表挂有区块

现在分配230h,group1的可用链表都不满足。则挖第二块32KB,有group2控制
group2使用情况为00000000 00000001H
在这里插入图片描述
回收相邻的内存块,进行合并

左侧第一张图中灰色部分表示待收回的区块300h,它的上下两部分为白色,表示已经回收过来的区块,可以合并。

判断下方为白色
指针找到自己的cookie大小300h,指针移动300h,到了下面一个区块的cookie位置,看最后1bit是否是为0,如果为0,表示可以和下面的区块合并。

下方区块为free,也为300h,合并为600h,如第二张图中间灰色部分

指针在自己cookie的位置,往上移动4字节,就找到上方区块的下cookie,判断最后1bit是否为0,若为0,就表示可以和上面的区块合并

三个300h合并大小为900h,900h = 2304D, 2304大于1024,所以挂在最后一个链表#63上,它用来处理大于1024B的区块

因此malloc每次分配都带有上下两个cookie,8个字节,第二讲的alloc就是为了去除每次分配的8个字节。(因为是给容器使用,容器的元素大小都相等,是已知的)

VC6内存管理free(p)

在这里插入图片描述
free回收,SBH要确定落在哪个header(共16个header)指定的1MB空间中,然后确定是这个header中的哪个group,然后确定这个group中的64条链表中的哪个链表

有一个sbh_pHeaderList指向16个header,判断p是否在某个header中,header有1MB的头指针,也知道实际创建的长度,即能算出尾指针,判断p是否在头尾之间
如果不在这个header,由于一个header大小是固定的,可以计算移动到下一个header,继续判断

用指针p减掉header头指针,除以32,即知道落在哪一段,如果从0算起,则减1.

确定在哪个group之后,把指针往上看,就知道cookie,知道自身是多大的内存块,确定在哪个链表

VC6内存管理总结

在这里插入图片描述
分段管理,一次要1MB的虚拟空间,虚拟空间分为32个group,一次要32KB,32KB是真正要到的内存。每个group再由64个链表控制

分段管理。分段的时候便于一段全部回收,然后还给操作系统

如果只有1个group管理1MB,这1MB切了许多块,要全部收回来之后才能还给操作系统

如果单位切的越小,则一段回收的机率越大

判断全回收:每个group中都有一个cntEntries,统计分配和回收的区块数量,当它为0的时候,意味着这个group全回收,这一段32KB就可以还给操作系统
在这里插入图片描述
cntEntries = 0的时候,说明区块都合并了,回到初始状态。此时不急着还给操作系统,有一个defering,延缓归还的操作。
有一个全回收的group时,先暂存,当有第二个全回收的group时,才释放前面那个group。
在这里插入图片描述
释放所有的内存块,SBH系统的面貌就是初始状态

从操作系统的API(这里是windows系统,比如HeapAlloc, VirtualAlloc),到CRT的malloc设计,再到std::allocator的底部实现,都有类似的链表管理结构

allocator设计成16个链表的目的不是提升分配的速度,而是为了去除malloc的cookie开销,减少malloc的次数,每一次malloc要一大块内存,然后切分成相等的区块,这样就可以去除每一小块的cookie。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值