首先对基于隐式空闲链表,使用立即边界标记合并方式实现的简单分配器进行分析。实验参考资料
隐式空闲链表的恒定形式如下图所示:
其结构为: 第一个字是不使用的填充字,使得双字的序言块(prologue block)边界对齐。这个特殊的序言块是一个8字节的分配块,仅由一个头部和一个脚部组成。序言块后面紧跟着零个或多个malloc创建free调用的普通块,在初始化时,这个块接近堆的大小。在堆的尾部是一个特殊的大小为零已分配的结尾块(epilogue block),只由一个头部组成。在初始化(mm_init)的时候,序言块、结尾块以及之间的大的空闲块(具有头部和尾部,是由extend_heap函数创建的)被创建,序言块永远不会释放。
(1)获得堆中空闲块的起始地址
void *mem_sbrk(int incr) {
char *old_brk=mem_brk;
if((incr<0)||((mem_brk+incr)>mem_max_addr)) {
errno=ENOMEM;
fprintf(stderr,"ERROR: mem_sbrk failed. Ran out of memory...\n");
return (void *) -1;
}
mem_brk+=incr;
return (void *)old_brk;
}
(2)定义具体地址访问操作实现方法
/*创建基本常量和操作,宏函数的名字应该能体现出函数的行为是什么*/
#define WSIZE 4 //一个字的大小,四个字节
#define DSIZE 8 //分配器的返回的块是8字节对齐的,块就是一次分配的大小,例如malloc(3),将返回块的大小是8
#define CHUNKSIZE (1<<12) //最大的空闲块
#define MAX(x,y) ((x) > (y)? (x) : (y))
/*将头部或尾部中保存的块的大小和3位标记位进行合并*/
#define PACK(size,alloc) ((size)|(alloc))
/*从地址p读或者写一个四字节的量*/
#define GET(p) (*(unsigned int *) (p))
#define PUT(p,val) (*(unsigned int *) (p)=(val))
/*从地址p获得块的大小以及查看块是已分配还是空闲的*/
#define GET_SIZE(p) (GET(p) & -0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
/*从空闲块(有效载荷)地址获得头部地址或者尾部地址*/
#define HDRP(bp) ((char *)(bp) - WSIZE)
#define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)//这里减DSIZE是因为加上GET_SIZE,多加了一个头部