堆区内存分配

通常我们在动态开辟内存时,会使用malloc函数在堆区开辟空间。

#include <stdlib.h>

void *malloc(size_t size);
// 成功返回已开辟空间的地址,否则返回NULL

堆区

如图堆区在未初始化的数据区(.bss)之后的开始,用于程序员动态开辟释放的数据区。为了减少内存碎片、降低系统调用开销,系统会为堆区分配一大块内存,将堆区分为多个不同大小的块来维护。每个块都是一段连续的虚拟内存,其中包含已分配和空闲的内存块。对于已分配的内存块,它可以被显式释放(free)或者分配器隐式释放。

 

隐式空闲链表

分配器通过特定的数据结构组织堆区的数据块,这种数据结构也被嵌入到块本身。单个堆块结构如下所示:

通过此结构将堆区组织成连续的、块大小不同的已分配和空闲块的序列。

此结构被称为隐式空闲链表(因为空闲块是通过头部中的大小字段隐含地连接着)。

优点:简单。

缺点:对链表的任何操作所花费的时间与空闲块和已分配块的总数成线性关系;

          在对块的管理上要求分配4字节大小的头部,加上双字节的对齐约束条件,即使仅需要1字节,但分配器会创建8个字节的块。所以显然对于请求较少的内存时,会出现严重的资源浪费。

 

边界标记

当已分配的块使用完被释放后需要进行相邻空闲块的合并,这样可以避免假碎片的情况(多个相邻的小的空闲块总和可以满足某个请求,但是由于它们是相邻的,且任意一个无法满足请求的情况)。

对于合并时机有两种:

  • 立即合并。就字面意思理解,当释放某一内存块时,与其相邻空闲块进行合并。
  • 推迟合并。就是释放块后,并不立即与相邻空闲块合并,而是等到某一时机再合并。例如:某个分配请求失败后,分配器会遍历整个堆区,合并所以的空闲块。

通过之前对隐式空闲链表的了解,如果我们所释放的空闲块在相邻的空闲块之前(如图所示),那么我们很容易通过当前块头部找到下一空闲块头部,检查其是否为空闲块,再将其大小加到当前块头部大小上,实现合并。

当如果相反情况,那我们无法简单地知道其头部位置,而是需要对整个堆区进行遍历,记录上一空闲块位置,直到找到当前块为止。所以此方法耗费大量时间,效率低。

所以分配器会使用边界标记法,即就是不光有头部,而且添加与头部结构相同的脚部。如图,可以很容易看出其作用与头部类似,都能快速的合并相邻空闲块。

虽然边界标记的合并效率提高,但由于又多添加了一个脚部,使得内存开销增大。

 

显示空闲链表

因为块分配与堆块的总数呈线性关系,所以隐式空闲链表不适合管理空闲块。(试想一下,随着堆区不断分配,已分配块越来越多,而空闲块越来越少,那么之后再根据请求查找空闲块可能就需要花费很长时间去查找,所以效率很低。)

因此在管理空闲块时,使用的时显示空闲链表。首先其数据结果与隐式空闲链表有所不同(如图为显式空闲链表单个块的结构)

可以看出分配块结构未变,而空闲块在原有效载荷内增加两个指针:pred(前驱)、succ(后继)。通过这两个指针就可以把空闲块组织成一个双向链表结构。这样我们在查找空闲块时就不需要遍历整个堆区。

显示链表也有缺点:首先空闲块必须足够大,因为要包含头部、脚部、前驱、后继四个指针。进而导致更大的最小块大小,潜在地提高了内部碎片的程度。

 

分离的空闲链表

之前我们已经将空闲块组织成一个双向链表,但使用此链表查找、分配空闲块的时间与空闲块数量成线性关系

解决方式就是通过分离存储:就是维护多个空闲链表,每个链表中的块的大小大致相等。即将所有可能的块大小分成一些等价类(大小类)。

分配器通过空闲链表数组来管理这些空闲链表(大小类)。

分离方式有很多种如:简单分离存储、分离适配。

        简单分离存储的大小类的空闲链表包含大小相等的块,块大小为大小类中最大元素的大小。分配和释放块都是常数时间,不分割,不合并,已分配块不需要头部和脚部,空闲链表只需是单向的,因此最小块为单字大小。缺点是很容易造成内部和外部碎片。

  分离适配的分配器维护一个空闲链表的数组,每个链表和一个大小类相关联,包含大小不同的块。分配块时,确定请求的大小类,对适当的空闲链表做首次适配。如果找到合适的块,可以分割它,将剩余的部分插入适当的空闲链表中;如果没找到合适的块,查找更大的大小类的空闲链表。分离适配方法比较常见。这种方法既快、内存利用率也高。

 

特别推荐《深入理解计算机系统》,本篇主要对其动态内存分配部分学习的总结,图片也都截取该书。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值