CMU15-213 malloc lab

CMU15-213 malloc lab

两种实现方式的源码链接:malloc GitHub
https://github.com/WellYixuanDu/CMU15-213/tree/main/malloclab-handout

该lab主要是实现一个动态内存申请器,即在堆中向对象分配内存,它主要由malloc、free、realloc这三个函数组成。
一开始os会分配一大块内存,但由于反复的内存申请与释放,会将一大块内存变为多个小块,为了对这些小块进行控制,我们通常会选取链表这一数据结构来管理这块内存,将小内存从头到尾通过链表的方式给连接起来。
连接方式通常有两种:
隐式空闲链表:隐式空闲链表即将占用的和未被占用的内存都连接在同一条链表上,无法直接找出空闲的区域,往往需要通过从头到尾的遍历才能找到空闲块,因此它也叫隐式的。
在这里插入图片描述
如上图所示,链表的每个元素都由上图所示,首先有一个头部,用于记录块的大小以及内存的是否分配。由于我们一般会强制块大小为双字对齐,因此块头部低三位地址总为0,即块大小的低三位总是0,所以我们可以将最低位设为标志位,用它来标识该块为0还是为1,相关宏定义如下。

#define WORD_SIZE (4)
#define DWORD_SIZE (8)
#define PACK_SIZE_ALLOC(size, alloc) (size | alloc)
#define GET_WORD(p) (*((int *)p))
#define PUT_WORD(p, val) ((*(int *)p) = val)
#define GET_SIZE(blk_ptr) (GET_WORD((void *)blk_ptr-WORD_SIZE) & ~0x7)
#define GET_ALLOC(blk_ptr) (GET_WORD((void *)blk_ptr - WORD_SIZE) &0x01)
#define GET_VALID_SIZE(blk_ptr) (GET_SIZE(blk_ptr) - 0x8)
#define HEADER_PTR(blk_ptr) ((void*)blk_ptr - WORD_SIZE)
#define PUT_HEADER_WORD(blk_ptr, size, alloc) PUT_WORD(HEADER_PTR(blk_ptr), PACK_SIZE_ALLOC(size, alloc))

同时,由于在释放空闲块的时候需要进行空闲块的合并,在合并过程中不仅仅需要考虑后面块是否空闲还需要考虑前面块是否空闲,因此,每次合并都需要进行遍历来找到前面的块,以得出它的状态,加大了时间复杂度,因此Knuth提出了一种边界标记的技术,允许在常数时间内进行对前面块的合并。如下图所示。
在这里插入图片描述
通过维护一个和头部一摸一样的尾部,这样就可以使得当前块通过上一个块的尾部来进行状态的得到,相关宏定义如下所示。

#define FOOTER_PTR(blk_ptr) ((void*)blk_ptr + GET_SIZE(blk_ptr) - DWORD_SIZE)
#define PUT_FOOTER_PTR(blk_ptr, size, alloc) PUT_WORD(FOOTER_PTR(blk_ptr), PACK_SIZE_ALLOC(size, alloc))
#define NEXT_BLOCK_PTR(blk_ptr) ((void *)blk_ptr + GET_SIZE(blk_ptr))
#define PREV_BLOCK_PTR(blk_ptr) ((void *)blk_ptr - GET_SIZE(blk_ptr - WORD_SIZE))

显式空闲链表:显式空闲链表,顾名思义,即通过一个双向链表将空闲块连接组织起来,空闲块与非空闲块的结构如下图所示。
在这里插入图片描述
对于分配块来说,与隐式空闲链表是完全一样的,因为分配块不需要维护在链表里进行遍历,free我们可以根据传进来的指针进行对应的直接操作。
对于空闲块来说,则有一个pred以及succ指针来进行相对应的遍历,以进行malloc操作。

#define PUT_PREV_PTR_WORD(blk_ptr, address) PUT_WORD(blk_ptr, address)
#define PUT_NEXT_PTR_WORD(blk_ptr, address) PUT_WORD(blk_ptr + WORD_SIZE, address)

隐式空闲链表 + 首次适配 (55分)

对于这种方法,理清了整体的流程,明白对应的操作需要进行什么样的步骤,接下来就是模拟实现了。首次适配便是通过malloc请求资源的大小,从头节点(虚拟链表)开始逐步往后遍历,找到第一个大于请求资源大小的空闲块即可将对应的节点地址返回了。需要注意的是在请求完资源后,需要对该块剩余内存再生成一个结点,以免内存资源的浪费。

显式空闲链表 + 分离适配 (85分)

对于这种方式来说,与上一种方式主要需要考虑的便是空闲链表的维护,我们为空闲链表维护了32种不同大小范围的空闲区域,使得内存请求可以根据自身大小快速找到适合自己的内存空间,一定程度上提高了时间效率。

struct block_node {
    struct block_node* prev_ptr;
    struct block_node* next_ptr;
}block_node;

struct block_node_list
{
    struct block_node block_node_begin;
    struct block_node block_node_end;
}block_node_list[32];

以上便是维护空闲块所需的数据结构。
源码见开头的github链接。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值