CSAPP malloc lab

本文档描述了实现自定义内存分配器的过程,采用分离存储的策略,维护16个大小类别的空闲链表。当需要扩展堆时,会检查新块是否能与前一块合并以减少碎片。malloc通过调整请求大小并对其对齐来工作,free则从空闲链表中删除相应块。realloc负责重新分配内存,如果剩余空间足够,则将其分割成新的空闲块,优化内存利用率。

整体的设计:使用分离存储的方案,维护一个存储空闲链表的数组free_block_list,每个大小类一个空闲链表,根据2的幂来划分大小,块的设计采用书本上介绍的显示空闲链表的方案。

当可供使用的空间不够时,就会扩展堆。在extend_heap中会增加堆顶的大小,并将新的空闲块挂载到free_block_list上。为了减少内存碎片,会根据新申请的块的前一个块是否空闲来决定是否与其合并。

void *extend_heap(size_t size)
{
    void *ptr;
    // align
    size = ALIGN(size);
    // 增加堆的大小
    if ((ptr = mem_sbrk(size)) == (void *)-1)
        return NULL;

    // 初始化新申请的空闲块
    PUT(HDRP(ptr), PACK(size, 0));
    PUT(FTRP(ptr), PACK(size, 0));
    /* 注意这个块是堆的结尾,所以还要设置一下结尾 */
    PUT(HDRP(NEXT_BLKP(ptr)), PACK(0, 1));
    // 将新申请的空闲块挂到free_block_list上
    insert_node(ptr, size);
    // 合并空闲块
    return merge_free_block(ptr);
}

insert_node实现了将一个空闲块插入到free_block_list的操作,free_block_list中一共有16条链表,下标由0到15升序排列,free_block_list中的元素指向队列的队尾
。将ptr插入block_free_list,链表从大到小排序,在insert_node中逆序遍历链表查找到对应的位置,将空闲块插入。需要注意的是,当链表为空时free_block_list中对应的元素为NULL。

void insert_node(void *ptr, size_t size)
{
    int idx = 0;
    void *next_node = NULL;
    void *pre_node = NULL;

    // 找到相应的桶
    for (;(idx < FREE_LIST_LENGTH - 1) && (size > 1); size >>= 1)
        idx++;
    next_node = free_block_list[idx];

    // next_node为pre_node的前一个结点
    for (; (next_node != NULL) && (size > GET_SIZE(HDRP(next_node))); next_node = GET_PRED_NODE(next_node))
        pre_node = next_node;

    if (next_node != NULL && pre_node != NULL) {    // case 1
        ASSIGN(GET_PRED_ADDR(ptr), next_node);
        ASSIGN(GET_NEXT_ADDR(next_node), ptr);
        ASSIGN(GET_NEXT_ADDR(ptr), pre_node);
        ASSIGN(GET_PRED_ADDR(pre_node), ptr);
    }
    else if (next_node != NULL && pre_node == NULL) {   // case 2
        ASSIGN(GET_PRED_ADDR(ptr), next_node);
        ASSIGN(GET_NEXT_ADDR(next_node), ptr);
        ASSIGN(GET_NEXT_ADDR(ptr), NULL);
        free_block_list[idx] = ptr;
    }
    else if (next_node == NULL && pre_node != NULL) {   // case 3
        ASSIGN(GET_PRED_ADDR(ptr), NULL);
        ASSIGN(GET_NEXT_ADDR(ptr), pre_node);
        ASSIGN(GET_PRED_ADDR(pre_node), ptr);
    }
    else if (next_node == NULL && pre_node == NULL) {   // case 4
        ASSIGN(GET_PRED_ADDR(ptr), NULL);
        ASSIGN(GET_NEXT_ADDR(ptr), NULL);
        free_block_list[idx] = ptr;
    }
}

delete_node实现了从free_block_list中取走一个空闲块的操作,寻找对应块的操作与insert_node中的类似。

// 从空闲链表中取出一个块
void delete_node(void *ptr)
{
    int idx = 0;
    size_t size = GET_SIZE(HDRP(ptr));

    // 找到对应的桶
    for (; (idx < FREE_LIST_LENGTH - 1) && (size > 1); size >>= 1)
        idx++;

    if (GET_PRED_NODE(ptr) != NULL && GET_NEXT_NODE(ptr) != NULL) { // case 1
        ASSIGN(GET_NEXT_ADDR(GET_PRED_NODE(ptr)), GET_NEXT_NODE(ptr));
        ASSIGN(GET_PRED_ADDR(GET_NEXT_NODE(ptr)), GET_PRED_NODE(ptr));
    } 
    else if (GET_PRED_NODE(ptr) != NULL && GET_NEXT_NODE(ptr) == NULL) {    // case 2
        ASSIGN(GET_NEXT_ADDR(GET_PRED_NODE(ptr)), NULL); 
        free_block_list[idx] = GET_PRED_NODE(ptr);
    }
    else if (GET_PRED_NODE(ptr) == NULL && GET_NEXT_NODE(ptr) != NULL) {    // case 3
        ASSIGN(GET_PRED_ADDR(GET_NEXT_NODE(ptr)), NULL);
    }
    else if (GET_PRED_NODE(ptr) == NULL && GET_NEXT_NODE(ptr) == NULL) {    // case 4
        free_block_list[idx] = NULL;
    }
}

merge_free_block实现的是合并相邻块的操作,这个函数只会实现与ptr相邻的空闲块的合并,合并的流程为:确认相邻的空闲块,计算连续空闲块的大小,确认新的空闲块的头部的位置,重置新的空闲块头部中的大小信息,最后将空闲块插入free_block_list。

void *merge_free_block(void *ptr)
{
    unsigned short prev_allocated = GET_ALLOC(HDRP(PREV_BLKP(ptr)));
    unsigned short next_allocated = GET_ALLOC(HDRP(NEXT_BLKP(ptr)));
    size_t size = GET_SIZE(HDRP(ptr));

    // 将相邻的空闲块合并
    if (prev_allocated && next_allocated)   // case 1
        return ptr;
    else if (prev_allocated && !next_allocated) {  // case 2
        delete_node(ptr);
        delete_node(NEXT_BLKP(ptr));
        size += GET_SIZE(HDRP(NEXT_BLKP(ptr)));
        PUT(HDRP(ptr), PACK(size, 0));
        PUT(FTRP(ptr), PACK(size, 0));
    }
    else if (!prev_allocated && next_allocated) {   // case 3
        delete_node(ptr);
        delete_node(PREV_BLKP(ptr));
        size += GET_SIZE(HDRP(PREV_BLKP(ptr)));
        PUT(FTRP(ptr), PACK(size, 0));
        PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0));
        ptr = PREV_BLKP(ptr);
    }
    else {  // case 4
        delete_node(ptr);
        delete_node(PREV_BLKP(ptr));
        delete_node(NEXT_BLKP(ptr));
        size += GET_SIZE(HDRP(PREV_BLKP(ptr))) + GET_SIZE(HDRP(NEXT_BLKP(ptr)));
        PUT(HDRP(PREV_BLKP(ptr)), PACK(size, 0));
        PUT(FTRP(NEXT_BLKP(ptr)), PACK(size, 0));
        ptr = PREV_BLKP(ptr);
    }

    // 将合并好的空闲块加入到空闲链接表中 
    insert_node(ptr, size);

    return ptr;
}

place中实现的是对ptr所指向的空闲块的使用,首先要从空闲链表中将对应的空闲块删除掉。为了充分利用空间,会将空闲块中未使用的部分与使用的部分分割开,并将未使用的部分成为一个新的空闲块挂载到free_block_list上,但是如果剩余部分无法容纳空闲块的信息就会放弃分割的策略。根据一些参考资料,发现将较小的空闲块放在同一边有利于减少碎片率,所以分割时对于小于256B的块会统一放在前面。

void *place(void *ptr, size_t size)
{
    size_t ptr_size = GET_SIZE(HDRP(ptr));
    // 剩余块的大小
    size_t unused_size = ptr_size - size;

    // 从空闲链表中删除该结点
    delete_node(ptr);

    // 若分离出来的空间不足以容纳header和footer和指向前后块的指针
    if (unused_size < 16) {
        PUT(HDRP(ptr), PACK(ptr_size, 1));
        PUT(FTRP(ptr), PACK(ptr_size, 1));
    }
    else if (size >= 256) {
        // 剩余的空间放在前面
        PUT(HDRP(ptr), PACK(unused_size, 0));
        PUT(FTRP(ptr), PACK(unused_size, 0));  

        PUT(HDRP(NEXT_BLKP(ptr)), PACK(size, 1));
        PUT(FTRP(NEXT_BLKP(ptr)), PACK(size, 1));
        insert_node(ptr, unused_size);
        return NEXT_BLKP(ptr);
    }
    else {
        PUT(HDRP(ptr), PACK(size, 1));
        PUT(FTRP(ptr), PACK(size, 1));

        // 剩余空间放在后面
        PUT(HDRP(NEXT_BLKP(ptr)), PACK(unused_size, 0));
        PUT(FTRP(NEXT_BLKP(ptr)), PACK(unused_size, 0));
        insert_node(NEXT_BLKP(ptr), unused_size);
    }
    return ptr;
}

mm_malloc实现了从可用堆空间中分配size B给调用者的操作,实现的流程为先在free_block_list中寻找到合适的空闲块,如果free_block_list中不存在足够大的空闲块,通过extend_heap函数来扩展堆的大小,将新的空闲块分配给调用者。

void *mm_malloc(size_t size)
{
if (size == 0)
return NULL;
// align
if (size <= DSIZE)
size = 2 * DSIZE;
else
size = ALIGN(size + DSIZE);

int idx = 0;
size_t searchsize = size;
void *ptr = NULL;

// 寻找空闲块
for (idx = 0; idx < FREE_LIST_LENGTH; idx++) {
    if (((searchsize <= 1) && (free_block_list[idx] != NULL))) {
        // 在桶中寻找空闲块
        for (ptr = free_block_list[idx]; (ptr != NULL) && ((size > GET_SIZE(HDRP(ptr)))); )
            ptr = GET_PRED_NODE(ptr);
        
        // 找到可用的空闲块
        if (ptr != NULL)
            break;
    }
    searchsize >>= 1;
}

// 找不到合适的空闲块,扩展堆的容量
if (ptr == NULL) {
    if ((ptr = extend_heap(MAX(size, CHUNKSIZE))) == NULL)
        return NULL;
}

// 使用空闲块
ptr = place(ptr, size);

return ptr;

}

mm_free实现了回收一个已分配块的操作,流程为将该块的头部和脚部中的分配信息设置为未分配,然后插入到free_block_list中。为了减少内存碎片,在最后会检查是否有可合并的相邻块,如果有则合并。

void mm_free(void *ptr)
{
size_t size = GET_SIZE(HDRP(ptr));

PUT(HDRP(ptr), PACK(size, 0));
PUT(FTRP(ptr), PACK(size, 0));

// 将回收的块插入free_block_list
insert_node(ptr, size);
// 合并相邻的块
merge_free_block(ptr);

}

mm_realloc实现了为ptr中的内容移到一个大小至少为size的块,如果原来的块的大小大于size,直接返回原来的ptr。内存拷贝是非常影响分配器性能的一个操作,为了避免内存拷贝,检查ptr的下一个块是否为空闲,如果是空闲的则计算两个块加起来的大小是否大于size,如果大于size在mm_realloc函数中直接合并两个块,并返回原来的ptr。如果以上的方法行不通,调用malloc分配一个新的块,并将原来块中的内容拷贝过去,返回新的块的地址。

void *mm_realloc(void *ptr, size_t size)
{
void *new_block = ptr;
int unused_size;

if (size == 0)
    return NULL;

// align
if (size <= DSIZE)
    size = 8;
else
    size = ALIGN(size + DSIZE);

// 如果当前的块够用
if (GET_SIZE(HDRP(ptr)) >= size)
    return ptr;
else if (!GET_ALLOC(HDRP(NEXT_BLKP(ptr))) && GEdeT_SIZE(HDRP(NEXT_BLKP(ptr))) + GET_SIZE(HDRP(ptr)) >= size) { // 检查是否下一个相邻块是否可用
    // 若下一个相邻块可以提供足够的空间,直接合并这两个块
    delete_node(NEXT_BLKP(ptr));
    unused_size = GET_SIZE(HDRP(NEXT_BLKP(ptr)));
    PUT(HDRP(ptr), PACK(GET_SIZE(HDRP(ptr)) + unused_size, 1));
    PUT(FTRP(ptr), PACK(GET_SIZE(HDRP(ptr)) + unused_size, 1));
}
else {
    // 申请与之前不连续的块,并将之前的内容拷贝过去
    new_block = mm_malloc(size);
    memcpy(new_block, ptr, GET_SIZE(HDRP(ptr)));
    mm_free(ptr);
}

return new_block;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值