您当前的位置: 首页 > 

phymat.nico

暂无认证

  • 1浏览

    0关注

    1967博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

基于malloc与free函数的实现代码及分析

phymat.nico 发布时间:2017-12-31 20:06:23 ,浏览量:1

用于内存管理的malloc与free这对函数,对于使用C语言的程序员应该很熟悉。前段时间听说有的IT公司以“实现一个简单功能的malloc”作为面试题,正好最近在复习K&R,上面有所介绍,因此花了些时间仔细研究了一下。毕竟把题目做出来是次要的,了解实现思想、提升技术才是主要的。本文主要是对malloc与free实现思路的介绍,蓝色部分文字是在个人思考中觉得比较核心的东西;另外对于代码的说明,有一些K&R上的解释,使用绿色加亮。

  在研究K&R第八章第五节的实现之前,不妨先看看其第五章第四节的alloc/afree实现,虽然这段代码主要目的是展示地址运算。

复制代码 代码如下:
alloc实现

#define ALLOCSIZE 10000 static char allocbuf[ALLOCSIZE];    /*storage for alloc*/ static char *allocp = allocbuf;    /*next free position*/

char *alloc(int n) {     if(allocbuf+ALLOCSIZE - allocp >= n) {         allocp += n;         return alloc - n;     } else         return 0; }

void afree(char *p) {     if (p >= allocbuf && ps.ptr; ;prevp = p, p= p->s.ptr) {         if(p->s.size >= nunits) { /* big enough */             if (p->s.size == nunits)  /* exactly */                 prevp->s.ptr = p->s.ptr;             else {                 p->s.size -= nunits;                 p += p->s.size;                 p->s.size = nunits;             }             freep = prevp;             return (void*)(p+1);         }         if (p== freep) /* wrapped around free list */             if ((p = morecore(nunits)) == NULL)                 return NULL; /* none left */     } }

   实际分配的空间是Header大小的整数倍,并且多出一个Header大小的空间用于放置Header。但是直观来看这并不是nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1啊?如果用(nbytes+sizeof(Header))/sizeof(Header)+1岂不是刚好?其实不是这样,如果使用后者,nbytes+sizeof(Header)%sizeof(Header) == 0时,又多分配了一个Header大小的空间了, 因此还要在小括号里减去1,这时才能符合要求。

   malloc()第一次调用时建立一个退化链表base,只有一个大小是0的空间,并指向它自己。freep用于标识空闲链表的某个元素,每次查找时可能发生变化;中间的查找和分配过程是基本的链表操作,在空闲链表中不存在合适大小的空闲空间时调用morecore()获得更多内存空间;最后的返回值是空闲空间的首地址,即Header之后的地址,这个接口与库函数一致。

复制代码 代码如下:
morecore()

#define NALLOC 1024    /* minimum #units to request */ static Header *morecore(unsigned nu) {     char *cp;     Header *up;     if(nu < NALLOC)         nu = NALLOC;     cp = sbrk(nu * sizeof(Header));     if(cp == (char *)-1)    /* no space at all*/         return NULL;     up = (Header *)cp;     up->s.size = nu;     free((void *)(up+1));     return freep; }

  morecore()从系统申请更多的可用空间,并加入。由于调用了sbrk(), 系统开销比较大,为避免morecore()本身的调用次数,设定了一个NALLOC,如果每次申请的空间小于NALLOC,就申请NALLOC大小的空间,使得后续malloc()不必每次都需要调用morecore()。 对于sbrk(),在后面会有介绍。

  这里有个让人惊讶的地方:malloc()调用了morecore(),morecore()又调用了free()!第一次看到这里时可能会觉得不可思议,因为按照惯性思维,malloc()和free()似乎应该是相互分开的,各司其职啊?但请再思考一下,free()是把空闲链表进行扩充,而malloc()在空闲链表不足时,从系统申请到更多内存空间后,也要先把它们转化成空闲链表的一部分,再进行利用。这样,malloc()调用free()完成后面的工作也是顺理成章了。根据这个思想,后面是free()的实现。在此之前,还有几个morecore()自身的细节:

  1.如果系统也没有空间可以分配,sbrk()返回-1。cp是char *类型,在有的机器上char无符号,这里需要一次强制类型转换。

  2.morecore()调用的返回值看上去比较奇怪,别担心,freep会在free()中修改的。使用这个返回值也是为了在malloc()里的判断、p = freep的再次赋值的语句能够紧凑。

复制代码 代码如下:
free()

void free(void *ap) {     Header *bp,*p;     bp = (Header *)ap -1; /* point to block header */     for(p=freep;!(bp>p && bp< p->s.ptr);p=p->s.ptr)         if(p>=p->s.ptr && (bp>p || bps.ptr))             break;    /* freed block at start or end of arena*/     if (bp+bp->s.size==p->s.ptr) {    /* join to upper nbr */         bp->s.size += p->s.ptr->s.size;         bp->s.ptr = p->s.ptr->s.ptr;     } else         bp->s.ptr = p->s.ptr;     if (p+p->s.size == bp) {     /* join to lower nbr */         p->s.size += bp->s.size;         p->s.ptr = bp->s.ptr;     } else         p->s.ptr = bp;     freep = p; }

   free()首先定位要释放的ap对应的bp与空闲链表的相对位置,找到它的的最近的上一个和下一个空闲空间,或是当它在整个空闲空间的前面或后面时找到空闲链表的首尾元素。注意,由于malloc()的分配方式和free()的回收时的合并方式(下文马上要提到),可以保证整个空闲空间的链表总是从低地址逐个升高,在最高地址的空闲空间回指向低地址第一个空闲空间。

  定位后,根据要释放的空间与附近空间的相邻性,进行合并,也即修改对应空间的Header。两个if并列可以使得bp可以同时与高地址和低地址空闲空间结合(如果都相邻),或者进行二者之一的合并,或者不合并。

  完成了这三部分代码后(注意放到同一源文件中,sbrk()需要#include ),就可以使用了。当然要注意,命名和stdlib.h中的同名函数是冲突的,可以自行改名。

  第一次审视源码,会发现很多实现可能原先并没有想到:Header的结构和对齐填充、空间的取整、链表的操作和初始化(边界情况)、malloc()对free()的调用、由malloc()和free()暗中保证的链表地址有序等等,确实很值得玩味。另外再附上前文中提到的两个问题还有一些补充问题的简单思考:

1.Header与空闲空间相剥离,Header中包含一个指向其空闲空间的指针

  这样做未必不可,相应地算法需要改动。同时,由于Header和空闲空间不再相邻,sbrk()获得的空间也应该包含Header的部分,内存的分布可能会更加琐碎。当然,这也可能带来好处,即用其他数据结构对链表进行管理,比如按大小进行hash,这样查找起来更快。

2.关于sbrk()

  sbrk()也是库函数,它能使堆往栈的方向增长,具体可以参考:brk(), sbrk() 用法详解。

3.可以改进的方

  空闲空间的寻找是线性的,查找过程在内存分配中可以看作是循环首次适应算法,在某些情况下可能很慢;如果再建立一个数据结构,如hash表,对不同大小的空间进行索引,肯定可以加快查找本身,并且能实现一些算法,比如最佳匹配。但查找加快的代价是,修改这个索引会占用额外的时间,这是需要权衡的。

  morecore()中的最小分配空间是宏定义,在实际使用中完全可以作为参数传递,根据需要设定最小分配下限。

关注
打赏
1659628745
查看更多评论
立即登录/注册

微信扫码登录

0.0448s