欢迎使用CSDN-markdown编辑器

对于伙伴系统而言,有大小固定且相同的内存块,将大小相同的内存块链接在一起时,为了达到写平衡,使用双向链表实现,这样可以在回收回的内存块时,将其插入双向链表的链尾,在申请内存块时,从链头删除内存块;而将链接不同大小内存块的双向链表链接起来时,因为内存块大小种类固定所以使用顺序表实现,故使用顺序表加双向链表的形式实现伙伴系统。

双向链表设计思路及定义

细化而言,对于内存块,因为是在双向链表中,所以每个内存块中需要一个header,其中包含的信息有前驱 llink、后继 rlink;同时还需要一个标记内存块是否被占用的信息 tag,tag = 0 则内存块空闲,tag = 1则内存块占用;需要一个标记内存块大小的信息 kval,内存块大小为 2k

所以我们在实现内存池时,以一个header的大小16个字节为单位进行内存管理,每个单位称之为WORD.

Space

typedef struct WORD
{
    struct WORD *llink;
    int tag;
    int k;
    struct WORD *rlink;
}WORD, *Space;

顺序表设计思路及定义

对于顺序表,则需要一个记录内存块大小的信息 nodesize;一个记录链接与 nodesize 大小相同的内存块的双向链表的结点信息 first。

typedef struct HNode
{
    int size;
    WORD *next;
}HNode, *PHNode;

实现动态内存管理

内存初始化

sqlist

通过初始化实现如上图所示的内存池。同时在这里需要建立一个内存池头结点、全局变量 HEAD 记录内存池首地址,作用下文会有详细说明。

void InitMemory(PHNode *ppav)               /*初始化:创建一个需要进行内存管理的内存池*/
{
    WORD word[SIZE];
    HNode head[K];

    HEAD = word;//保存内存池首地址

    word[0].llink = word;//初始化内存池
    word[0].tag = 0;
    word[0].k = K - 1;
    word[0].rlink = word;

    *ppav = head;//将顺序表链接到头指针

    int i;
    int size = 1;

    for(i = 0; i < K; i++, size *= 2)//初始化顺序表
    {
        head[i].size = size;
        head[i].next = NULL;
    }
    head[K - 1].next = word;//将内存池链接到顺序表
}

动态内存申请

WORD *Mymalloc(PHNode ppav, int size)    /*动态内存申请*/
{
    assert(ppav != NULL);
    if (ppav == NULL)
    {
        return NULL;
    }

    int i;
    //int j = -1;

    for (i = 0; i < K; i++)
    {
        //if (ppav[i].size >= size && j == -1)
        //{
        //  j = i;
        //}
        if (ppav[i].next != NULL && ppav[i].size >= size)//判断是否有足够剩余空间
        {
            break;
        }
    }

    if (i == K)//剩余空间不足
    {
        return NULL;
    }

    Space q;
    Space head = ppav[i].next;
    Space p = head;

    //有剩余空间
    if (head->llink == head)//该双向链表中只有一个节点
    {
        //head = NULL; Error: 无法改变ppav[i].next的值,只改变head的值,函数运行完成后销毁,无用
        ppav[i].next = NULL;
    }
    else//该双向链表不只有一个节点
    {
        ppav[i].next = head->rlink;
        head->llink->rlink = head->rlink;
        head->rlink->llink = head->llink;
    }

    /*方法一: 从左向右由小到大切割内存块
    q = p + ppav[j].size;

    p->llink = p;
    p->k = j;
    p->tag = 1;
    p->rlink = p;

    while (j < i)
    {
        q->llink = q;
        q->tag = 0;
        q->k = j;
        q->rlink = q;

        ppav[j].next = q;

        j++;
        q = q + ppav[j].size;
    }
    */

    /*方法二: 从右向左有大到小切割内存块*/
    for(i--; ppav[i].size >= size; i--)//大块内存块切割小块内存块
    {
        q = p + ppav[i].size;

        q->llink = q;
        q->tag = 0;
        q->k = i;
        q->rlink = q;

        ppav[i].next = q;
    }

    p->llink = p;
    p->tag = 1;
    p->k = i + 1;
    p->rlink = p;

    return p;
}

动态内存申请时要注意:

  • 当双向链表值在顺序表中的头指针为NULL的情况
  • 双向链表只有一个节点的情况

动态内存申请的难点在于处理大块内存块切割小块内存块的情况,这时有两种方法,方法一: 从左向右由小到大切割内存块,方法二: 从右向左有大到小切割内存块。

动态内存回收

void Myfree(PHNode ppav, WORD *p)        /*动态内存回收*/
{
    p->tag = 0;

    int i;
    int k = p->k;
    int size = powx(2, k);
    Space q;

    int flag;

    while(k < K)
    {
        if ((p - HEAD) % (size * 2) == 0)//判断左块、右块
        {   
            flag = 0;//左块
            q = p + size;
        }
        else
        {
            flag = 1;//右块
            q = p - size;
        }

        if (q->tag == 0 && flag == 1 && q->k == p->k)//p为右块且有p的左块存在
        {
            if(q->rlink == q)
            {
                ppav[k].next = NULL;
            }
            else
            {
                ppav[k].next = q->rlink;//易错: 若p的左块为ppav[k].next所指的空间块,不写则在输出时会陷入死循环
                q->llink->rlink = q->rlink;
                q->rlink->llink = q->llink;
            }

            q->k++;
            p = q;
            k++;
        }
        else if (q->tag == 0 && flag == 0 && q->k == p->k)//p位左块且有p的右块存在
        {
            if(q->rlink == q)
            {
                ppav[k].next = NULL;
            }
            else
            {
                ppav[k].next = q->rlink;//易错: 若p的左块为ppav[k].next所指的空间块,不写则在输出时会陷入死循环
                q->llink->rlink = q->rlink;
                q->rlink->llink = q->llink;
            }

            p->k++;
            k++;
        }
        else//p没有伙伴块
        {
            break;
        }

        size *= 2;
    }

    if(ppav[p->k].next == NULL)
    {
        p->llink = p;
        p->rlink = p;

        ppav[p->k].next = p;
    }
    else
    {
        q = ppav[k].next;
        q->llink->rlink = p;//将p插入内存池
        p->llink = q->llink;
        q->llink = p;
        p->rlink = q;
    }
}

在动态内存回收中需要注意:

  • 在判断回收的内存块的伙伴是否空闲时应使用q->tag == 0 && flag == 0 && q->k == p->k进行判断,缺一不可
  • 代码第33、50行不可缺少
  • 当双向链表值在顺序表中的头指针为NULL的情况
  • 双向链表只有一个节点的情况

动态内存回收的难点在于内存回收时对于伙伴块的合并,应使用循环,当回收的内存块有伙伴时,与伙伴合并成为新的内存块,新的内存块再次查看是否有伙伴,直至新的内存块没有伙伴,将其插入和其大小的匹配的双向链表中,正如整体设计中所说,这时双向链表的价值就体现出来,为了写平衡,双向链表可轻易将新内存块插入双向链表的链表尾。

在这其中伙伴是一个很重要的概念,何为“伙伴”?将一个大块内存切割成为两个大小相同的小块内存,两个小块内存互为伙伴

在寻找回收的内存块的伙伴时,首先需要知道回收的内存块是左块还是右块, pMOD2k+1=0 则为左块,否则为右块。这里的 p 指回收的内存块相对于内存池首地址的相对地址,这时在内存初始化中的HEAD变量的作用就体现出来。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值