【CSAPP实验8】动态内存分配器

 目录

一、题目回顾

二、源代码

三、思路

四、理解分析与实现

         1、关于一些宏定义

2、辅助函数的实现

0️⃣辅助函数概览与声明

1️⃣拓展堆大小的函数

2️⃣添加空闲节点

3️⃣删除空闲节点

4️⃣合并block

5️⃣在ptr指向的block中 放置asize大小的数据

3、主要函数实现

1️⃣初始化

2️⃣malloc

3️⃣free

4️⃣realloc

五、结果呈现


一、题目回顾

         本实验的主要任务,是实现一个堆上的动态内存分配器,达到最高性能。 原理提示:可以把“堆”看成是一个线性的数组(地址0、1、2、3、4、5等)。malloc的目的,是从堆的空闲空间中,分配指定大小的空间,并返回一个指向该分配空间的指针。free的目的,是释放某个指针指向的空间。请参考教材9.9。 你需要修改mm.c,替换其中的malloc、free、realloc函数,并对trace进行运行评分。

       【拓展提高】建议考虑实现教材上的“伙伴系统”

二、源代码

         附到资源里面啦

三、思路

         总共四个主要函数,init、 malloc、 free、 realloc   根据课本和题目指示,利用空闲链表法隐式管理空闲的堆,注意利用好mem_sbrk()函数(⚠️:mem_sbrk()函数是一个用于动态内存分配的系统调用,它的作用是扩展或缩小进程的数据段的大小。具体来说,mem_sbrk()函数接收一个整数参数,表示需要增加或减少的内存大小,然后相应地调整进程的数据段边界。如果成功,mem_sbrk()会返回原来的数据段边界;如果失败,通常会返回-1,并且设置errno为ENOMEM,表示内存不足。)。根据老师提供的一些宏定义去补充新的宏定义,然后补充一些辅助函数(基础操作:比如拓展堆,这个很重要,堆的初始化和堆空间无法分配时需要调用;添加空闲节点;删除空闲节点;合并空闲节点;放置函数)其实大多数都是链表的操作。最后根据这些宏定义和辅助函数实现主要函数。

四、理解分析与实现

         1、关于一些宏定义

             懒得码字了,自己理解一下啵

/* 单字 (4) 还是双字 (8) 边界对齐 */
#define ALIGNMENT 8

/* 舍入到最近的ALIGNMENT边界上 */
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)

#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))

// My additional Macros

#define WSIZE     4          // word and header/footer size (bytes)
#define DSIZE     8          // double word size (bytes)
#define INITCHUNKSIZE 48     //针对最后一个数据做出的优化
#define CHUNKSIZE (1<<12)    //mm_malloc、realloc 每次向堆中申请内存的最低限制 !!
#define LISTLIMIT  20

#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define MIN(x, y) ((x) < (y) ? (x) : (y))

/* Pack a size and allocated bit into a word */
#define PACK(size, alloc) ((size) | (alloc))
/* Read and write a word at address p */
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p)=(val))
/* Read the size and allocated fields from address p */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp) ((char *)(bp)- WSIZE)
#define FTRP(bp) ((char *)(bp)+ GET_SIZE(HDRP(bp))- DSIZE)
/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp) ((char *)(bp)+ GET_SIZE(((char *)(bp)- WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp)- GET_SIZE(((char *)(bp)- DSIZE)))

#define SET_PTR(p, ptr) (*(unsigned int *)(p) = (unsigned int)(ptr))    // p 指针指向的[值]为 ptr 指针

// Read the size and allocation bit from address p

#define GET_SIZE(p)  (GET(p) & ~0x7)
#define GET_ALLOC(p)  (GET(p) & 0x1)
#define GET_TAG(p)  (GET(p) & 0x2)
#define SET_RATAG(p)   (GET(p) |= 0x2)
#define REMOVE_RATAG(p) (GET(p) &= ~0x2)

// Address of free block's predecessor and successor entries
#define PRED_PTR(ptr) ((char *)(ptr))
#define SUCC_PTR(ptr) ((char *)(ptr) + WSIZE)

// Address of free block's predecessor and successor on the segregated list
#define PRED(ptr) (*(char **)(ptr))
#define SUCC(ptr) (*(char **)(SUCC_PTR(ptr)))

2、辅助函数的实现

0️⃣辅助函数概览与声明

/* 全局变量 分离空闲链表 */
void* segregated_free_lists[LISTLIMIT];

static void print_block(int request_id, int payload);
static void* extend_heap(size_t size);
static void* coalesce(void* ptr);
static void* place(void* ptr, size_t asize);
static void insert_node(void* ptr, size_t size);
static void delete_node(void* ptr);

1️⃣拓展堆大小的函数

/* 拓展堆的大小size */
static void* extend_heap(size_t size)
{
    void* ptr;
    size_t asize;                // Adjusted size
    asize = ALIGN(size);         // 8字节对齐
    if ((ptr = mem_sbrk(asize)) == (void*)-1)
        return NULL;
    // 设置 header 和 footer
    PUT(HDRP(ptr), PACK(asize, 0));
    PUT(FTRP(ptr), PACK(asize, 0));
    PUT(HDRP(NEXT_BLKP(ptr)), PACK(0, 1));    //设置 终点block
    insert_node(ptr, asize);
    void* ans = coalesce(ptr);
    return ans;
}

首先,mem_sbrk 是模拟 sbrk 系统调用的自定义函数,用于增加堆的大小。(void*)-1 是一个特殊的返回值,通常表示错误。如果 mem_sbrk 失败,ptr 将被设置为 (void*)-1,函数将返回 NULL。

然后又利用宏定义里面的put设置header和footer,并设置结束block表明堆结束

插入、合并

注:extend_heap 在两种情况下被调用:1)当堆被初始化时;2)当mm_malloc 不能找到一个合适的匹配块时

2️⃣添加空闲节点

/* 添加空闲节点 */
static void insert_node(void* ptr, size_t size) {
    int list = 0;
    void* search_ptr = ptr;
    void* insert_ptr = NULL;
    // 选择适合大小的分离空闲链表
    while ((list < LISTLIMIT - 1) && (size > 1)) {
        size >>= 1;
        list++;
    }
    // 按照链表中block size的递增序 查找合适的插入位置
    search_ptr = segregated_free_lists[list];
    while ((search_ptr != NULL) && (size > GET_SIZE(HDRP(search_ptr)))) {
        insert_ptr = search_ptr;
        search_ptr = PRED(search_ptr);
    }
    // 将 ptr 指向的空闲 block 在 search_ptr(前驱)和insert_ptr(后继)之间插入
    if (search_ptr != NULL) {   //前驱不为NULL
        if (insert_ptr != NULL) {   //后继不为NULL
            SET_PTR(PRED_PTR(ptr), search_ptr); //设置前驱  [链表基本操作]
            SET_PTR(SUCC_PTR(search_ptr), ptr); //设置前驱的后继
            SET_PTR(SUCC_PTR(ptr), insert_ptr); //设置后继
            SET_PTR(PRED_PTR(insert_ptr), ptr); //设置后继的前驱
        }
        else {                //后继为NULl
            SET_PTR(PRED_PTR(ptr), search_ptr);
            SET_PTR(SUCC_PTR(search_ptr), ptr);
            SET_PTR(SUCC_PTR(ptr), NULL);
            segregated_free_lists[list] = ptr;  //此时需要改变 链表的头指针
        }
    }
    else {    //前驱为NULL
        if (insert_ptr != NULL) {   //后继不为NULL
            SET_PTR(PRED_PTR(ptr), NULL);
            SET_PTR(SUCC_PTR(ptr), insert_ptr);
            SET_PTR(PRED_PTR(insert_ptr), ptr);、
        }
        else {                    //后继为NULL
            SET_PTR(PRED_PTR(ptr), NULL);
            SET_PTR(SUCC_PTR(ptr), NULL);
            segregated_free_lists[list] = ptr;  //此时需要改变 链表的头指针
        }
    }
    return;
}

空闲链表是按照2的几次方排列的,最多2的LISTLIMIT大小,首先遍历找到大致空间。

进行链表的插入!!!注意修改前驱和后继

3️⃣删除空闲节点

/* 删除空闲节点 */
static void delete_node(void* ptr) {
    int list = 0;
    size_t size = GET_SIZE(HDRP(ptr));
    // 选择合适大小的分离空闲链表
    while ((list < LISTLIMIT - 1) && (size > 1)) {
        size >>= 1;
        list++;
    }
    //链表操作
    if (PRED(ptr) != NULL) {
        if (SUCC(ptr) != NULL) {
            SET_PTR(SUCC_PTR(PRED(ptr)), SUCC(ptr));
            SET_PTR(PRED_PTR(SUCC(ptr)), PRED(ptr));
        }
        else {
            SET_PTR(SUCC_PTR(PRED(ptr)), NULL);
            segregated_free_lists[list] = PRED(ptr);    //没有后继 需要改变头指针为前驱
        }
    }
    else {
        if (SUCC(ptr) != NULL) {
            SET_PTR(PRED_PTR(SUCC(ptr)), NULL);
        }
        else {
            segregated_free_lists[list] = NULL;     //前驱后继都是NULL 改变头指针为NULL
        }
    }
    return;
}

已经找到合适节点,把它分配出去,需要把它从空闲链表中删除

4️⃣合并block

/* 合并 block */
static void* coalesce(void* ptr)
{
    size_t prev_alloc = GET_ALLOC(HDRP(PREV_BLKP(ptr)));
    size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(ptr)));
    size_t size = GET_SIZE(HDRP(ptr));
    if (prev_alloc && next_alloc) {                         // Case 1 前后都已分配
        return ptr;
    }
    else if (prev_alloc && !next_alloc) {                   // Case 2 前分配 后未分配 合并后继
        delete_node(ptr);                                   // 在链表中删除原来两个node,将两个空闲块合并
        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_alloc && next_alloc) {                 // Case 3 前未分配 后已分配 合并前驱
        delete_node(ptr);                                   // 在链表中删除原来两个node,将两个空闲块合并
        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);                                   // 在链表中删除原来三个node,将三个空闲块合并
        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);     //将合并之后的新block 作为node插入 分离空闲链表
    return ptr;
}

      合并的实现:

         就是这几种情况的实现,找到相邻的空闲块,删除,合并(这里的合并直接用PUT宏定义实现就可以),插入

5️⃣在ptr指向的block中 放置asize大小的数据

static void* place(void* ptr, size_t asize)
{
    // 计算ptr指向的内存块的大小
    size_t ptr_size = GET_SIZE(HDRP(ptr));
    // 计算剩余空间大小
    size_t remainder = ptr_size - asize;
    // 从链表中删除当前节点
    delete_node(ptr);           

    // 如果剩余大小不超过16B,则最多只能放下free block的附加信息,则不切分
    if (remainder <= DSIZE * 2) {   
        // 标记内存块为已占用,大小为ptr_size
        PUT(HDRP(ptr), PACK(ptr_size, 1));
        PUT(FTRP(ptr), PACK(ptr_size, 1));
    }
    // 如果请求的空间大小大于等于110字节
    else if (asize >= 110) {    
        // 将内存块的后半部分分配给新请求
        PUT(HDRP(ptr), PACK(remainder, 0));
        PUT(FTRP(ptr), PACK(remainder, 0));
        PUT(HDRP(NEXT_BLKP(ptr)), PACK(asize, 1));
        PUT(FTRP(NEXT_BLKP(ptr)), PACK(asize, 1));
        // 将剩余部分插入链表
        insert_node(ptr, remainder);
        // 返回新分配的内存块的起始地址
        return NEXT_BLKP(ptr);
    }
    // 如果请求的空间大小小于110字节
    else {
        // 将内存块的前半部分分配给新请求
        PUT(HDRP(ptr), PACK(asize, 1));
        PUT(FTRP(ptr), PACK(asize, 1));
        PUT(HDRP(NEXT_BLKP(ptr)), PACK(remainder, 0));
        PUT(FTRP(NEXT_BLKP(ptr)), PACK(remainder, 0));
        // 将剩余部分插入链表
        insert_node(NEXT_BLKP(ptr), remainder);
    }
    // 返回原内存块的起始地址
    return ptr;
}

     具体含义在注释里都写了,直接看注释就好

3、主要函数实现

1️⃣初始化

/*
 * mm_init - 初始化malloc系统,此函数,在整个运行期间,只被调用1次,用于建立初始化环境
 */
int mm_init(void)
{
    int listnumber;
    char* heap;
    /* 初始化分离空闲链表 */
    for (listnumber = 0; listnumber < LISTLIMIT; listnumber++)
    {
        segregated_free_lists[listnumber] = NULL;
    }
    /* 初始化堆 */
    if ((long)(heap = mem_sbrk(4 * WSIZE)) == -1)
        return -1;
    /* 这里的结构参见本文上面的“堆的起始和结束结构” */
    PUT(heap, 0);
    PUT(heap + (1 * WSIZE), PACK(DSIZE, 1));
    PUT(heap + (2 * WSIZE), PACK(DSIZE, 1));
    PUT(heap + (3 * WSIZE), PACK(0, 1));
    /* 扩展堆 */
    if (extend_heap(INITCHUNKSIZE) == NULL)
        return -1;
    return 0;
}

堆的初始化,这个没什么好说的。就是把空闲链表初始化一下,然后初始化堆、扩展堆

2️⃣malloc

/*
 * mm_malloc - 通过增加brk指针,来分配一块内存。
 *     总是分配一块内存,它的大小是ALIGNMENT的整数倍(对齐)。
 */
void* mm_malloc(size_t size)
{
    if (size == 0)
        return NULL;
    /* 内存对齐 */
    if (size <= DSIZE)
    {
        size = 2 * DSIZE;
    }
    else
    {
        size = ALIGN(size + DSIZE);
    }
    int listnumber = 0;
    size_t searchsize = size;
    void* ptr = NULL;
    while (listnumber < LISTLIMIT)
    {
        /* 寻找对应链 */
        if (((searchsize <= 1) && (segregated_free_lists[listnumber] != NULL)))
        {
            ptr = segregated_free_lists[listnumber];
            /* 在该链寻找大小合适的free块 */
            while ((ptr != NULL) && ((size > GET_SIZE(HDRP(ptr)))))
            {
                ptr = PRED(ptr);
            }
            /* 找到对应的free块 */
            if (ptr != NULL)
                break;
        }
        searchsize >>= 1;
        listnumber++;
    }
    /* 没有找到合适的free块,扩展堆 */
    if (ptr == NULL)
    {
        if ((ptr = extend_heap(MAX(size, CHUNKSIZE))) == NULL)
            return NULL;
    }
    /* 在free块中allocate size大小的块 */
    ptr = place(ptr, size);
    return ptr;
}

首先进行内存对齐操作。如果size小于或等于DSIZE(可能是某个预设的最小分配单位),则将size调整为2 * DSIZE,否则调整size为ALIGN(size + DSIZE),其中ALIGN函数可能用于确保size是ALIGNMENT的整数倍。

然后在空闲链表中查找合适的块,在找到可能包含合适块的链表后,进一步在链表中查找

3️⃣free

/*
 * mm_free - 释放一块内存。其实没干啥事....
 */
void mm_free(void* ptr)
{
    size_t size = GET_SIZE(HDRP(ptr));
    PUT(HDRP(ptr), PACK(size, 0));
    PUT(FTRP(ptr), PACK(size, 0));
    /* 插入分离空闲链表 */
    insert_node(ptr, size);
    /* 注意合并 */
    coalesce(ptr);
}

4️⃣realloc

/*
 * mm_realloc - 重新扩展一块已分配的内存。仅仅是使用mm_malloc和mm_free来实现,很蠢
 */
void *mm_realloc(void *ptr, size_t size)
{
    void *new_block = ptr; 
    int remainder;
    if (size == 0)
        return NULL;
    /* 内存对齐 */
    if (size <= DSIZE)
    {
        size = 2 * DSIZE;
    }
    else
    {
        size = ALIGN(size + DSIZE);
    }
    /* 如果size小于原来块的大小,直接返回原来的块 */
    if ((remainder = GET_SIZE(HDRP(ptr)) - size) >= 0)
    {
        return ptr;
    }
    /* 否则先检查地址连续下一个块是否为free块或者该块是堆的结束块,因为我们要尽可能利用相邻的free块,以此减小“external fragmentation” */
    else if (!GET_ALLOC(HDRP(NEXT_BLKP(ptr))) || !GET_SIZE(HDRP(NEXT_BLKP(ptr))))
    {
        /* 即使加上后面连续地址上的free块空间也不够,需要扩展块 */
        if ((remainder = GET_SIZE(HDRP(ptr)) + GET_SIZE(HDRP(NEXT_BLKP(ptr))) - size) < 0)
        {
            if (extend_heap(MAX(-remainder, CHUNKSIZE)) == NULL)
                return NULL;
            remainder += MAX(-remainder, CHUNKSIZE);
        }
        /* 删除刚刚利用的free块并设置新块的头尾 */
        delete_node(NEXT_BLKP(ptr));
        PUT(HDRP(ptr), PACK(size + remainder, 1));
        PUT(FTRP(ptr), PACK(size + remainder, 1));
    }
    /* 没有可以利用的连续free块,而且size大于原来的块,这时只能申请新的不连续的free块、复制原块内容、释放原块 */
    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、付费专栏及课程。

余额充值