实现内存分配器

本文介绍了两种内存分配方法——边界标识法和伙伴系统。边界标识法较为灵活,但在内存回收方面存在不足。伙伴系统则通过合并伙伴块进行回收,虽然能有效减少内部碎片,但可能导致较多的外部碎片。

两种内存分配方法:边界标识法和伙伴系统
1、边界标识法
比较灵活,但回收不方便

boundary.h

#pragma once

typedef struct WORD
{
    union 
    {
        struct WORD *llink;
        struct WORD *uplink;
    };
    int tag;//0空闲1占用
    int size;
    struct WORD *rlink;
}WORD,*Head,*Foot,*Space;

#define MEM_SIZE 1024
#define e 10//分配的误差值
#define FootLoc(p) (p+p->size-1)//找到p的尾

Space CreatMem();

Space MyMalloc(Space pav,int n);

void MyFree(Space *ppav,WORD *p);

boundary.cpp

#include "boundary.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

Space CreatMem()
{
    WORD *pmem = (WORD *)malloc(MEM_SIZE * sizeof(WORD));
    assert(pmem != NULL);

    pmem->llink = pmem;
    pmem->rlink = pmem;
    pmem->tag = 0;
    pmem->size = MEM_SIZE;

    FootLoc(pmem)->uplink = pmem;
    FootLoc(pmem)->tag = 0;

    return pmem;
}

Space MyMalloc(Space *pav,int n)
{
    assert(pav != NULL);

    if(*pav==NULL)
    {
        return NULL;
    }

    WORD* p = *pav;

    do
    {
        if(p->size >= n)//找到了
        {
            break;
        }
        p = p->rlink;
    }while(p != *pav);

    if(p->size < n)
        return NULL;


    //分配后剩一个小碎片,全部分配,将节点从链表中整个删除
    if(p->size-n <= e)
    {
        if(p->rlink == p)//p是头节点
        {
            *pav = NULL;
        }
        else
        {
            *pav = p->rlink;
        }
        //将p从链表中删除
        p->llink->rlink = p->rlink;
        p->rlink->llink = p->llink;

        p->llink = p->rlink = p;
        p->tag = 1;
        FootLoc(p)->tag = 1;        
    }
    else//分配高地址
    {
        //将p 的尾巴分配出去,新的尾巴指向p,tag置为1
        p->size -= n;
        FootLoc(p)->uplink = p;
        FootLoc(p)->tag = 0;

        WORD *q = FootLoc(p)+1;
        q->llink = q->rlink = q;
        q->size = n;
        q->tag = 1;
        FootLoc(q)->uplink = q;
        FootLoc(q)->tag = 1;

        *pav = p->rlink;//为了达到写平衡
        p = q;
    }
    return p;
}

void MyFree(Space *ppav,WORD *p)
{
    assert(ppav != NULL);

    p->llink = p;
    p->rlink = p;
    p->tag = 0;
    FootLoc(p)->uplink = p;
    FootLoc(p)->tag = 0;

    Space pav = *ppav;
    if(pav == NULL)
    {
        *ppav = p;
        return;
    }

    int ltag = (p-1)->tag;
    int rtag = (FootLoc(p)+1)->tag;

    WORD *left;
    WORD *right;

    if(ltag==1 && rtag==1)//左右都占用,直接插入不合并
    {//p插入到链表的尾部
        left = pav->llink;
        right = pav;

        left->rlink = p;
        p->llink = left;
        p->rlink = right;
        right->llink = p;
    }
    else if(ltag==0 && rtag==1)//左占用右空闲接到左边的尾巴
    {
        left = (p-1)->uplink;
        left->size += p->size;
        FootLoc(left)->uplink = left;
        *ppav = left->rlink;
    }
    else if(ltag==1 && rtag==0)//左占用右空闲接到右边的上边
    {
        right = (FootLoc(p)+1);

        //将p插入链表
        right->llink->rlink = p;
        right->rlink->llink = p;
        p->llink = right->llink;
        p->rlink = right->rlink;

        p->size += right->size;
        FootLoc(p)->uplink = p;

        *ppav = p->rlink;
    }
    else//左右都空闲 三块合并
    {
        left = (p-1)->uplink;
        right = (FootLoc(p)+1);

        //将right从链表中删除
        right->llink->rlink = right->rlink;
        right->rlink->llink = right->llink;

        //合并三块内存
        left->size = left->size+p->size+right->size;
        FootLoc(left)->uplink = left;
        *ppav = left->rlink;
    }
}

test.cpp


void ShowUsed(WORD *p)
{
    if(p == NULL)
        return;
    printf("占用块信息:\n");
    printf("起始地址=%d,大小=%d,tag=%d,结束地址=%d,尾巴tag=%d\n",
        p->uplink,p->size,p->tag,FootLoc(p)->uplink,FootLoc(p)->tag);
}

void ShowUnUsed(Space pav)
{
    if(pav == NULL)
    {
        printf("\n没有空闲块\n");
        return;
    }
    WORD *p = pav;
    do
    {
        printf("空闲块信息:\n");
        printf("起始地址=%d,大小=%d,tag=%d,结束地址=%d,尾巴tag=%d\n",
        p->uplink,p->size,p->tag,FootLoc(p)->uplink,FootLoc(p)->tag);
        p = p->rlink;
    }while(p!=pav);
}
int main()
{
        Space pav = CreatMem();

    WORD *p1 = MyMalloc(&pav,100);
    WORD *p2 = MyMalloc(&pav,200);
    WORD *p3 = MyMalloc(&pav,300);
    WORD *p4 = MyMalloc(&pav,400);

    ShowUsed(p1);
    ShowUsed(p2);
    ShowUsed(p3);
    ShowUsed(p4);
    ShowUsed(p5);

    ShowUnUsed(pav);

    MyFree(&pav,p2);
    MyFree(&pav,p3);
    WORD *p5 = MyMalloc(&pav,500);
    ShowUsed(p1);
    ShowUsed(p2);
    ShowUsed(p3);
    ShowUsed(p4);
    ShowUsed(p5);

    ShowUnUsed(pav);
}

2、伙伴系统
伙伴系统需要直到以下几个概念:
(1)伙伴:将一个大块,分割成两个相等的子块,这两个子块互为伙伴。
(2)回收:只合并伙伴
(3)直到一个内存块的地址,可以求出它伙伴的地址
如果内存块p为左块,则伙伴的地址等于p+p->size
如果内存块p为右块,则伙伴的地址等于p-p->size
(4)如何直到p是左块还是右块?
自己的地址除以自己的大小,偶数为左块( (p/p->size)%2==0 ),奇数为右块( (p/p->size)%2==1)
(5)合并时不能和自己伙伴以外的内存块合并,因为无法算出它的起始地址
(6)合并伙伴块时,找到伙伴的地址还要同时判断两个块的大小是否相等
(7)不是所有的地址都从0开始,所以要减去自己的地址

伙伴系统的优点:不需要尾,节约空间
伙伴系统的缺点:内存碎片比较多

这又提到了一个内存碎片的概念。小小的提一下内存碎片。
内存碎片通常分为内部碎片和外部碎片:
(1). 内部碎片是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免;
(2). 外部碎片是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。
简单的举一个例子说明一下:
内部碎片:因为所有的内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。假设某个客户请求7字节的内存块时,因为没有合适大小的内存,所以它会获得8字节的内存块。那么多余出来的内存空间就叫内存碎片
外部碎片:假设有一块一共有100个单位的连续空闲内存空间,第一次申请了10个单位,而第二次申请了5个单位。如果把第一块内存块释放,然后再申请一块大于10个单位的内存块,比如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。如果10~14一直被占用,而以后申请的空间都大于10个单位,那么前是个单位就永远用不上了,变成外部碎片。
这里写图片描述
partner.h

#pragma once
#define m 10//10种规格的内存

typedef struct WORD_b
{
    struct WORD_b *llink;
    int tag;//0空闲,1占用
    int kval;//节点大小的指数
    struct WORD_b *rlink;
}WORD_b,*Head;

typedef struct HeadNode
{
    int nodesize;//节点的大小
    struct WORD_b *first;//指向链表的开头,链表为双向循环链表
}FreeList[m+1],*PFreeList;

void InitMem(PFreeList pfreelist,Head pmem);

WORD_b *MyMalloc(PFreeList pfreelist,int n);

void MyFree(PFreeList pfreelist,WORD_b *p);

partner.cpp

#include <stdio.h>
#include "partner.h"
#include <assert.h>

static WORD_b *pbagin;

void InitMem(PFreeList pfreelist,Head pbuf)
{
    assert(pfreelist!=NULL && pbuf!=NULL);

    for(int i=0;i<m+1;++i)
    {
        pfreelist[i].nodesize = 1<<i;
        pfreelist[i].first = NULL;
    }

    //初始化规格最大的链表
    pbuf[0].llink = pbuf;
    pbuf[0].rlink = pbuf;
    pbuf[0].kval = m;
    pbuf[0].tag = 0;

    pfreelist[m].first = pbuf;
    pbagin = pbuf;
}

WORD_b *MyMalloc(PFreeList pfreelist,int n)
{
    WORD_b *p;
    WORD_b *q;

    int i;
    for(i=0;i<m+1;i++)
    {
        if(pfreelist[i].nodesize>=n && pfreelist[i].first!=NULL)
        {
            break;
        }
    }

    //没找到,分配失败
    if(i == m+1)
    {
        return NULL;
    }

    p = pfreelist[i].first;//可分配子表的第一个节点

    if(p == p->rlink)//p是唯一一个结点
    {
        pfreelist[i].first = NULL;//分配后该子表变成空表
    }
    else
    {
        //从子表中删除*p节点
        p->llink->rlink = p->rlink;
        p->rlink->llink = p->llink;
        pfreelist[i].first = p->rlink;
    }

    //将剩余块插入到相应链表中
    for(i=i-1;pfreelist[i].nodesize>=n;i--)
    {
        q = p+pfreelist[i].nodesize;
        q->llink = q;
        q->rlink = q;
        q->tag = 0;
        q->kval = i;
        pfreelist[i].first = q;//该链表一定为NULL
    }

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

    return p;
}

static WORD_b *Buddy(WORD_b *p)
{
    WORD_b *q;
    if(((p-pbagin)/(1<<p->kval))%2 == 0)
    {
        q = p+(1<<p->kval);
    }
    else
    {
        q = p-(1<<p->kval);
    }

    if(q->tag==0 && q->kval==p->kval)
    {
        return q;
    }
    else
    {
        return NULL;
    }
}

void MyFree(PFreeList pfreelist,WORD_b *p)
{
    p->llink = p;
    p->rlink = p;
    p->tag = 0;

    WORD_b *q = Buddy(p);
    //合并伙伴块
    while(q != NULL)
    {
        //将q从链表中删除
        if(q->rlink == q)
        {
            pfreelist[p->kval].first = NULL; 
        }
        else
        {
            q->llink->rlink = q->rlink;
            q->rlink->llink = q->llink;
            pfreelist[p->kval].first = q->rlink;
        }

        //p,q合并
        if(p < q)//p为左块
        {
            p->kval++;
        }
        else
        {
            q->kval++;
            p = q;
            p->llink = p;
            p->rlink = p;
        }
        q = Buddy(p);
    }

    //将p 插入链表
    if(pfreelist[p->kval].first ==  NULL)
    {
        pfreelist[p->kval].first = p;
    }
    else
    {
        q = pfreelist[p->kval].first;
        q->llink->rlink = p;
        p->rlink = q;

        p->llink = p;
        p->rlink = p;

    }
}

test.h

#include <stdio.h>
#include "partner.h"

void ShowUsed(WORD_b *p)
{
    if(p == NULL)
    {
        return;
    }
    printf("占用块信息:\n");
    printf("  地址=%d,tag=%d,kval=%d\n",p,
                p->tag,p->kval);
}

void ShowUnused(PFreeList plist)
{
    WORD_b *p;
    printf("未占用块信息:\n");
    for(int i=0;i<m+1;i++)
    {
        if(plist[i].first != NULL)
        {
            printf("大小=%d的空闲块信息:\n",plist[i].nodesize);
            p = plist[i].first;
            do
            {
                printf("  地址=%d,tag=%d,kval=%d\n",p,
                p->tag,p->kval);
                p = p->rlink;
            }while(p != plist[i].first);
        }
    }

}

int main()
{
    WORD_b buf[1<<m];//内存池
    FreeList freelist;//顺序表

    InitMem(freelist,buf);

    WORD_b *p1 = MyMalloc(freelist,10);
    WORD_b *p2 = MyMalloc(freelist,10);
    WORD_b *p3 = MyMalloc(freelist,10);
    WORD_b *p4 = MyMalloc(freelist,10);
    MyFree(freelist,p1);
    p1 = NULL;
    ShowUsed(p1);
    ShowUsed(p2);
    ShowUsed(p3);
    ShowUsed(p4);

    ShowUnused(freelist);


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值