malloc的简单实现

/*
要想实现内存的动态管理,我们需要简单了解一下程序的"运行"空间.
在程序"运行"时,内存空间.分为几段.
代码段.放可执行的汇编指令
数据段.存放用来初始化的数据.
BSS段.用来存放未初始化的数据与初始化值为0的数据.
 
栈段.用来存放程序的栈
 
因为栈的增长方向是从高地址到低地址。所以一般栈段。都是放在程序的最后。代码段放在最前。数据段紧跟其后。然后是BSS段。
 
从BSS到最后的栈,中间有很大一片空白空间。如果你的栈用的足够多。这个空间慢慢会被栈所填充。
 
但这个过程,是一个缓慢的。并且是程序不合理才能造成这样的情况。除非是递归调用,程序的调用并不太深,所以很少的有限的栈保证调用与局部变量的分配就足够使用了.
所以中间的空白区域。我们可以利用一下。从BSS段结束后。我们建立一个堆。堆的增长方式是从低地址到高地址。所以我们可以在看着堆与栈向一起增长。这是一个有趣的事情。有效的提高 了内存的利用率。
 
 
 一个简单的分配内存方案。我们设计这样一个函数。
 
void *heap=0x3ff; //堆开始的地址;
void *mov=heap;
void * malloc(size){
     void p=mov;
     mov+=size;
     return p;
}

 

 
这个函数每次都把一块内存按传递进来的大小.分割出去.并把指针返回给需要的人.
 
这样是可以的,但也是死板的.因为它分配出去的内存,不具有可重用性.分出去就完了.人家用完了也不回收.一直到内存全部分配完毕.没有内存可用.
 
所以这个函数是不实用的.我们需要建立一个,可分配,可回收的机制.让使用完毕的内存,可以再次得到利用.
 
最简单的办法,莫过于标志,我们在内存中设置一个标志,这个标志包含了,当前内存块的大小,下一个内存块的位置,以及是否在使用.
 
这样,我们就可以简单的写出另一个函数.free.当这块内存不再使用时,我们用free.把这块内存标记为未使用.这样,遍历到这块内存时,我们就可以把这块内存分配给其它人使用了.
 
而且 malloc函数也需要修改一下.首先遍历内存.查找大于申请内存的块.然后在这个块里切出合适的内存.并返回这个内存的指针.
 
并做好标志.
 
但这样做也有问题.首先就是内存块会被越切越小.被切出的部分被称为外部碎片.外部碎片无法得到利用.直到切出的内存再也不够使用.而外部碎片却占了绝大部分.
 
而越切越小,越切越碎的过程中,遍历内存也成了一件耗费大量时间的事情.
 
所以我们现在面对了越来越复杂的局面.我们需要解决.外部碎片的可重利用问题,还要解决遍历时间的问题.
 
首先解决碎片,我们需要在我们的标志里放两个指针.一个指针指向它的上一块内存.一个指针指向它的下一块内存.
而且为了内存不会被切的过于细碎,我们要设定一个最小分配额度.这样,在free解放内存时,我们只要检查一下左右的邻居是不是小于最小分配额度,只要是小于额度的,就被认为是碎片,被回收掉.大于额度的会被留下等待分配.
 
并且我们要还要检查左右两块是不是已经释放的块.如果是释放的块.我们还要把小块合并成大块.
 
这样就基本解决了碎片问题,虽然外总碎片仍然存在,但是它已经可以被回收.可以被重新利用,我们向成功迈进了一步.现在解决遍历的问题:
 
从堆的开始,用遍历的方式.去查找可用内存的代价是非常大的.如果这个空闲块在尾部,我们可能需要进行几M次的比较才能找到内存.
 
决定这个次数的是.已经分配内存的块数加下空闲的块数.
 
所以有人就提出一个办法.,我们把空闲块.用一个指针连接起来.这样,我们遍历的时候就不用再去遍历那些已经在使用的块,这样,就可以节省大量的时间,这个方案是正确的.也是最初时或是简单需要时,使用最多的方案.
 
所以我们在以上的基础上.加一个指针.在free时.我们可以释放空间,剔出碎片.合并相连空闲块.再把这个空闲块加入到空闲链表里.
 
这样我们就完成了基础的内存申请与回收的机制.我们可以简单的使用自己写的malloc与free了.
 
//程序的实现:实现一个链表式的简单malloc
 
/*************************************************************************
    > File Name: mmalloc.c
    > Author: 五号智能
    > Mail: xgywd@foxmail.com 
    > Created Time: 2016年11月17日 星期四 12时47分41秒
 ************************************************************************/
 
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
 
#define USED 1
#define USEN 0
typedef unsigned int uint;
//内存池的起始与终结
void *mem_start = NULL;
void *mem_end = NULL;
 
/*
要实现malloc.我们需要一点点数据结构的想象,
首先,我们用一个结构体来代表我们内存中的这个"块"
无论是已经分配的内存还是,未分配的内存.还是空闲的内存,它们都有着共同的特征我们把这个共同的特征抽象出来.形成为一个对象*/
//内存结构
typedef struct _block{
    uint flg;         //标记
    struct _block *prev;       //前一块内存
    struct _block *next;        //后一块内存
    struct _block *fprev;       //空闲链表中前一块空闲
    struct _block *fnext;       //空闲链表中后一块空闲
    uint size;                  //当前这块内存的大小
    uint used;                  //当前这块内存是否在使用.
}_block;
 
 
 
typedef struct _block * block;  //给它的指针定义一个好用的类型.
 
static block FREE = NULL;    //用来记录空闲
static block HEAD = NULL;    //用于调试时观察.无具体作用.
//构造函数是简单的,只是把这个内存对象的各种元素赋值.
static void create_block(   block this, //地址
                            block prev, //前一个块
                            block next, //后一个块
                            block fprev,//前一个空闲块
                            block fnext,//后一个空闲块
                            uint used,  //是否是空闲
                            size_t size)//块的大小
{
    this->flg = 0xfdfdfdfd;
    this->prev = prev;
    this->next = next;
    this->fprev = fprev;
    this->fnext = fnext;
    this->used = used;
    this->size = size;
 
}
//从当前块切出一块分配出去.
static block cutmem(block free, size_t size);
//把自身插入到空闲列表
static void ins_block(block free);
//消除碎片与合并左右空闲
static block remove_chip(block free);
//把自身从空闲列表里删除.
static void remove_free_block(block free);
//寻找空闲块
static block findfree(block free, size_t size);
 
 
/*
 切块的方法:首先.一个内存块的开始位置,肯定是记录这块内存的结构体对象.所以我们需要在内存开始地址加上
 结构体对象大小,再加上申请的大小,的这个地址.上建立一个新的内存对象.来描述剩余内存.
 
*/
//从当前块切出一块分配出去.
static block cutmem(block free, size_t size)
{
    block p;           //建立一个临时的内存指针p
    p = (block)((char*)free+ size + sizeof(_block)); //p的位置等于对象结构体地址加上申请内存大小,再加上对象大小. 
    create_block(   p,                //在p的位置.我们构造一个新的内存对象.
                    free,             //这个对象的前一个块是当前切割的块
                    free->next,       //这个对象的后一个块.是当前切割块的后一个块.
                    NULL,             //空闲前等于空
                    NULL,             //空闲后等于空
                    USEN,             //空闲标记等于空闲.
                    free->size - size -sizeof(_block));   //空闲大小,等于当前块大小减分配大小,再减去对象大小
    free->used = USED;      //当前被切割的块成为被使用的块.
    free->next = p;         //当前块的下一个块等于 新构造的块
    free->size = size;      //当前块的大小,等于分配大小.
    
    remove_free_block(free);//把当前块从空闲链表删除.
    ins_block(p);           //把分割出来的空闲块插入空闲链表
    return free;            //把分割好的块返回
}
//把自身插入到空闲列表
static void ins_block(block free)      //free要插入的块
{
    if(FREE!=NULL){                   //如果空闲链表不为空
        free->fnext = FREE;           //把free插入到空闲链表最前.
        FREE->fprev = free;           //原来的最前的父等于被插入链表
    }
    FREE = free;            //把FREE指针指向新的最前.另一个意义,如果FREE为空.FREE=被插入的块.
}
//消除碎片与合并左右空闲
static block remove_chip(block free)
{
    
    if(free->used != USEN)
        //如果当前块正在使用,则返回当前块
        return free;
    if(free->next->size < 8  || (free->next->used == USEN))  //如果当前块的下个块小于8,证明是碎片.或下个块是空闲块
    {
        free->size += free->next->size + sizeof(_block);        //合并当前块与下个块.
        remove_free_block(free->next);
        free->next = free->next->next;
    }
    if(free->prev)                                  //如果当前块前一个块不为空
        if (free->prev->used == USEN  || free->prev->size < 8 ) //判断前一个块是不是空闲,或是碎片,如果是
            free = remove_chip(free->prev);           //递归调用自己进行处理
    return free;                       //返回处理后的指针.
}
//把自身从空闲列表里删除.
static void remove_free_block(block free)
{
    if(FREE==free)                         //如果FREE=要删除的块.
        FREE=free->fnext;                   //FREE向后移动.如果不移动.FREE会被删除,脱离空闲链表
    
    if(free->fprev)               //如果删除块的前一个空闲块在
        free->fprev->fnext = free->fnext;//则前一个空闲的下一个指针,等于要删除块的下一个
    
    if(free->fnext)                 //如果删除块的下一个存在.
        free->fnext->fprev = free->fprev; // 则下一    个的前一个等于删除块的前一个
    
    free->fprev = NULL;    // 删除块从空闲链表删除后.把它的空闲前后指针归0;
    free->fnext = NULL;
}
//寻找空闲块
static block findfree(block free, size_t size)//给我一个空闲指针.与要的大小
{
    
    while (free){                        //如果free不为空则循环
        if (free->size >= size + sizeof (_block))     //如果free的大小,大于等于分配大小加标志大小
            return cutmem(free, size);     //则找到空闲块.切割后,返回处理后的指针
        else
            free = free->fnext;    //切换到空闲链表下一个元素.
 
        if(free== FREE)               //如果free等于空闲链表首元素.证明已经找了一圈没找到合适大小.返回空
            return NULL;
    }
    return NULL;          //如果free为空,还没找到.返回空
}
 
void *mmalloc(size_t size)
{
    if (NULL == mem_start)           //如果内存池不存在.返回空.
        return NULL;
 
    size = (size + 7) & ~7;          //分配为8的倍数
    if (!(((block)mem_start)->flg == 0xfdfdfdfd)){      //如果内存池中的标记不存在,进行初始化.构造第一个对象
        create_block(   (block) mem_start, 
                        NULL, 
                        NULL, 
                        NULL, 
                        NULL, 
                        USEN, 
                        (size_t)((char*) mem_end - (char*)mem_start+1));
        HEAD = FREE = (block)mem_start;                    //空闲链表指向这块创建好的内存
    }                                                        
    
    
    //如果内存标记存在.则调用查找内存函数.函数返回值加上对象大小,就是分配的地址,强制为void指针返回
    return (void*)((char*)findfree(FREE, size)+sizeof(_block));
}
//释放内存
void mmfree(void *mp)
{
    block free = (block)((char*)mp -sizeof(_block));     //free指针等于释放地址减去对象大小.对准标记
    free->used =USEN;              //把当前块标记为未使用
    free = remove_chip(free);     //消除碎片与合并左右空闲
    ins_block(free);             //把当前块插入空闲链表.
}
 
 
//测试函数
int main ()
{
    int i,j;
 
    char *p[10];
    mem_start = malloc(0x600000);
    mem_end = (char*)mem_start + 0x600000-1;
 
    
    
    
    for(i = 0; i<10; i++)           //测试分配
    {
        p[i]=(char*)mmalloc(i*10+1);
    }
    for(i = 0; i<10; i++)               //测试释放.
    {
        mmfree(p[i]);
    }
 
 
    free(mem_start);
    return 0;
}

 

 这个程序并没有经过严格测试,所以使用者一定要小心哦~
 

 

posted on 2017-06-02 10:31  五号智能  阅读(400)  评论(0)    收藏  举报

导航