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; }
这个程序并没有经过严格测试,所以使用者一定要小心哦~
浙公网安备 33010602011771号