建议先学会书本的隐式空闲链表, 再学此显式空闲链表

显式空闲链表(双向空闲链表)的堆块格式


使用显式空闲链表使得首次适配的分配时间从块总数(n)的线性时间减少到了空闲块数量(m)的线性时间:O(m) < O(n),其中m < n。释放块和合并块与隐式空闲链表一样都是O(1)。

显示空闲链表的格式

宏marco

#define WSIZE 4
#define DSIZE 8
#define CHUNKSIZE (1 << 12)#define MAX(x, y) ((x) > (y) ? (x) : (y))/* Pack a size and allocated bit into a word */
#define PACK(size, alloc) ((size) | (alloc))/* Read and write a word at address p */
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p) = (val))/* Read the size and allocated fields from address p */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp) ((char *)(bp) - WSIZE)
#define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE)))#define NEXT_NODEPTR(bp) ((char *)(bp) + WSIZE)
#define PREV_NODEPTR(bp) ((char *)(bp))

只有最后两句是新增的(其余与书本一致),PREV_NODEPTR(bp)表示PREV指针,NEXT_NODEPTR(bp)表示NEXT指针

函数原型

4个基本操作函数:

int mm_init(void);               // 创建带一个初始空闲块的堆
void *mm_malloc(size_t size);
void mm_free(void *ptr);
void *mm_realloc(void *ptr, size_t size);

其他函数:

static void *extend_heap(size_t words);      // 用一个新的空闲块扩展堆
static void *coalesce(void *bp);            // 合并
static void *find_fit(size_t asize);        // 首次适配
static void place(void *bp, size_t asize);  // 放置
void make_lifo(char *ptr);                  // LIFO(新free的块放表头)
void fix_ptr(char *ptr);                    // 调整prev、next指针

两个全局变量:

static char *heap_listp = NULL;
static char *root = NULL;

函数详解

首先是mm_init():

int mm_init(void)
{/* Creat the initial empty heap */if ((heap_listp = mem_sbrk(6 * WSIZE)) == (void *)-1)return -1;PUT(heap_listp, 0);PUT(heap_listp + (1 * WSIZE), 0);   // Prev_nodeptrPUT(heap_listp + (2 * WSIZE), 0);   // Next_nodeptrPUT(heap_listp + (3 * WSIZE), PACK(DSIZE, 1));PUT(heap_listp + (4 * WSIZE), PACK(DSIZE, 1));PUT(heap_listp + (5 * WSIZE), PACK(0, 1));root = heap_listp + (1 * WSIZE);heap_listp += (4 * WSIZE);/* Extend the empty heap with a free block of CHUNKSIZE bytes */if ((extend_heap(CHUNKSIZE / DSIZE)) == NULL)return -1;return 0;
}

与书上的隐式链表不同的是,函数需要从内存系统中得到6个字,上面的格式图有提到

entend_heap():

static void *extend_heap(size_t words)
{char *bp;size_t size;/* Allocate an even number of words to maintain alignment */size = (words % 2) ? (words + 1) * DSIZE : words * DSIZE;if ((long)(bp = mem_sbrk(size)) == (void *)-1)return NULL;/* Initialize free block header/footer and the epilogue header */PUT(HDRP(bp), PACK(size, 0));    // Free block headerPUT(FTRP(bp), PACK(size, 0));   // Free block footerPUT(NEXT_NODEPTR(bp), 0);       // Next_nodeptrPUT(PREV_NODEPTR(bp), 0);       // Prev_nodeptrPUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); // New epilogue headerreturn coalesce(bp);
}

与之前的size不同,必须是双字(DSIZE)的偶倍数,当然最小块格式依然是16字节

mm_malloc与之前一致,除了调用extend_heap里要除以DSIZE:

void *mm_malloc(size_t size)
{size_t asize;      // Adjusted block sizesize_t extendsize;    // Amount to extend heap if no fitchar *bp;/* Ignore spurious requests */if (size == 0)return NULL;/* Adjust block size to include overhead and alignment reqs. */if (size <= DSIZE){asize = 2 * (DSIZE);}else{asize = (DSIZE) * ((size + (DSIZE) + (DSIZE - 1)) / (DSIZE));}/* Search the free list for a fit */if ((bp = find_fit(asize)) != NULL) {place(bp, asize);return bp;}/* No fit found. Get more memory and place the block */extendsize = MAX(asize, CHUNKSIZE);if ((bp = extend_heap(extendsize / DSIZE)) == NULL)return NULL;place(bp, asize);return bp;
}

mm_realloc与之前一致:

void *mm_realloc(void *ptr, size_t size)
{size_t oldsize;void *newptr;/* If size == 0 then this is just free, and wo return NULL */if (size == 0) {mm_free(ptr);return 0;}/* if oldptr is NULL, then this is just malloc */if (ptr == NULL)return mm_malloc(size);newptr = mm_malloc(size);/* If realloc fails the original block is left untouched */if (!newptr)return 0;/* Copy the old data. */oldsize = GET_SIZE(HDRP(ptr));if (size < oldsize) oldsize = size;memcpy(newptr, ptr, oldsize);/* Free old bolck */mm_free(ptr);return newptr;
}

mm_free新增两句,把PREV、NEXT指针置空:

void mm_free(void *bp)
{if (bp == 0)return;size_t size = GET_SIZE(HDRP(bp));PUT(HDRP(bp), PACK(size, 0));PUT(FTRP(bp), PACK(size, 0));PUT(NEXT_NODEPTR(bp), 0);PUT(PREV_NODEPTR(bp), 0);coalesce(bp);}

coalesce合并函数:

static void *coalesce(void *bp)
{size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp)));size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));size_t size = GET_SIZE(HDRP(bp));if (prev_alloc && next_alloc) {                     // Both is not free      // case1                        //return bp;}else if (!prev_alloc && next_alloc) {               // Pre is freesize += GET_SIZE(HDRP(PREV_BLKP(bp)));     // case 2fix_ptr(PREV_BLKP(bp));PUT(FTRP(bp), PACK(size, 0));PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));bp = PREV_BLKP(bp);}else if (prev_alloc && !next_alloc) {               // Next is freesize += GET_SIZE(HDRP(NEXT_BLKP(bp)));       // case 3fix_ptr(NEXT_BLKP(bp));PUT(HDRP(bp), PACK(size, 0));PUT(FTRP(bp), PACK(size, 0));}else {                                              // Both is freesize += GET_SIZE(FTRP(NEXT_BLKP(bp))) + GET_SIZE(HDRP(PREV_BLKP(bp)));                     // case 4fix_ptr(PREV_BLKP(bp));fix_ptr(NEXT_BLKP(bp));PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));bp = PREV_BLKP(bp);}make_lifo(bp);return bp;
}

coalesce是变化最大的,各个case里都要调用fix_ptr()调整PREV、NEXT指针

下图红色代表PREV指针,绿色代表NEXT指针
case1:前后都是已分配块,它的处理方法调用make_lifo就行

case2:前块为空闲块,后块为已分配块

case3:前块为已分配块,后块为空闲块

case4:前后都是空闲块

coalesce的最后还要实现LIFO:调用make_lifo(),使得当前要free的块成为表头块

make_lifo如下,实现原理就是case1:

inline void make_lifo(char *ptr)
{char *oldhead = GET(root);if (oldhead != NULL)PUT(PREV_NODEPTR(oldhead), ptr);PUT(NEXT_NODEPTR(ptr), oldhead);PUT(root, ptr);
}

接下来再看fix_ptr():

inline void fix_ptr(char *ptr)
{char *prevptr = GET(PREV_NODEPTR(ptr));char *nextptr = GET(NEXT_NODEPTR(ptr));if (prevptr == NULL) {              // ptr's block is a head_blockif (nextptr)                    // make nextptr become headPUT(PREV_NODEPTR(nextptr), 0);PUT(root, nextptr);}else {                              // ptr's block is not a head_blockif (nextptr)PUT(PREV_NODEPTR(nextptr), prevptr);PUT(NEXT_NODEPTR(prevptr), nextptr);}PUT(NEXT_NODEPTR(ptr), 0);PUT(PREV_NODEPTR(ptr), 0);}

第一个if里面考虑的是:要free的块是表头块
else里面考虑的是:要free的块不是表头块

find_fit()就是改成了指针遍历的方式:

static void *find_fit(size_t asize)
{char *t = GET(root);while (t != NULL) {if (asize <= GET_SIZE(HDRP(t)))return t;t = GET(NEXT_NODEPTR(t));}return NULL;   // No fit
}

place 新增需要改变指针状态:

static void place(void *bp, size_t asize)
{size_t csize = GET_SIZE(HDRP(bp));fix_ptr(bp);if ((csize - asize) < (2 * DSIZE)) {     // No cutPUT(HDRP(bp), PACK(csize, 1));PUT(FTRP(bp), PACK(csize, 1));}else {                                        // Need to cutPUT(HDRP(bp), PACK(asize, 1));PUT(FTRP(bp), PACK(asize, 1));bp = NEXT_BLKP(bp);PUT(HDRP(bp), PACK(csize - asize, 0));PUT(FTRP(bp), PACK(csize - asize, 0));PUT(NEXT_NODEPTR(bp),0);PUT(PREV_NODEPTR(bp),0);coalesce(bp);}
}

注意一开始要调用fix_ptr(bp),因为该块已经被分配,所以要把它的空闲块状态给移除,并作出一些指针调整

运行

①将上述函数写好在mm.c里
②终端输入make
③读README运行两个short文件
④运行trace file:./mdriver -a -g -v -t traces/
trace file可从github上获取,搜索:malloclab traces

注意

1.make过程中会有许多cast warnning,因为有些地方没有进行指针转换
2.运行过程中通常有两个bug:

1) Segmentation Fault:一般是访问了非法地址
2) Payload Overlap:有效载荷重叠,一般是某些指针的值有问题

CSAPP:malloclab (显式空闲链表 LIFO+首次适配)相关推荐

  1. 显示空闲链表和隐式空闲链表_使用空闲资源添加Espresso UI测试

    显示空闲链表和隐式空闲链表 You may want to read the Spanish version of this article in Droid-Latam's publication ...

  2. CSAPP:MallocLab

    Malloc Lab 做什么? 实现一个内存分配器 怎么做? 非常建议看完书后,自己写一遍,进步非常大,可以检测出你哪块理解不够深刻,可以将这块知识点吃的很透彻.在遇到瓶颈的时候看看人家怎么写的,不然 ...

  3. 论文浅尝 | 用于视觉推理的显式知识集成

    论文笔记整理:刘克欣,天津大学硕士 链接:https://openaccess.thecvf.com/content/CVPR2021/papers/Zhang_Explicit_Knowledge_ ...

  4. 操作系统之文件管理:5、文件物理结构(连续分配、链式(显式、隐式)分配、索引分配(链接、多层索引、混合索引))

    3.文件物理结构 思维导图 文件块.磁盘块 文件分配方式 1.连续分配 2.链接分配 隐式链接 显式链接 3.索引分配 如果一个文件的大小超过一个磁盘块怎么办? 1.链接方案 2.多层索引 3.混合索 ...

  5. Bundle与Intent机制,Intent显式/隐式意图,传递参数及参数序列化,各种跳转(如打开浏览器),Intent的Size

    intent传递有没有大小限制,是多少?- http://blog.csdn.net/wingichoy/article/details/50679322 Android Intent调用 Uri的使 ...

  6. JUC-9.“锁”事(显式锁与隐式锁/悲观锁与乐观锁/公平锁与非公平锁/可重入锁/读写锁(独占/共享/降级)/邮戳锁/死锁)、锁升级

    目录 一.悲观锁与乐观锁 1.1 悲观锁 1.2 乐观锁 二.公平锁与非公平锁 2.1 为什么会有公平锁/非公平锁的设计为什么默认非公平? 2.2 如何选择使用哪种锁? 三.可重入锁(又名递归锁) 3 ...

  7. 弹道分析软件_5分钟读懂显式有限元分析工具Ansys LS-DYNA

    LS-DYNA是LSTC的旗舰产品,专注于计算速度和精度,数十年来一直是汽车行业耐撞性和乘客安全仿真的黄金标准,其擅长仿真材料在承受短时高强度载荷时的响应,如碰撞.跌落以及金属成型过程中发生的情况.2 ...

  8. CoreAnimation4-隐式动画和显式动画

    事务 Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画.动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在. 当你改变CA ...

  9. html显示数据库图片django,django将图片上传数据库后在前端显式的方法

    1.使用ImageField先安装pillow模块 pip install pillow 2.在app的models中设置 class Image(models.Model): pic_name=mo ...

  10. iactionresult 图片_从显式类型的ASP.NET Core API控制器(不是IActionResult)返回404

    在ASP.NET Core 2.1中使用return null;解决了此问题: public ActionResult Get(int id) { Thing thing = GetThingFrom ...

最新文章

  1. java swing原理浅析
  2. 关于Puppet不得不说的故事
  3. faster rcnn源码解读总结
  4. 计算机相关冷门专业,211名校冷门专业和双非计算机专业,该如何选择?过来人告诉你...
  5. linux 移植qt,Linux下移植QT(2)---移植QT
  6. 电场 大学_人工电场优化算法
  7. 千兆网综合布线系统的线缆选型
  8. 运行项目到 微信开发者工具和浏览器
  9. while语句的使用
  10. python与线性代数 矩阵方程
  11. 《Android安全技术揭秘与防范》——第2章,第2.1节钱从哪里来
  12. C# WinForm 数据库连接及对数据库的相关操作(未使用证实)
  13. 熊猫可用人脸识别?大熊猫迎来熊生高光时刻,以后终于可以认清我了
  14. 谷歌关闭中国音乐搜索服务--有点可惜
  15. 『TensorFlow』TFR数据预处理探究以及框架搭建
  16. Unity 抛物线运动脚本(弓箭轨迹)
  17. 经验总结|一个移动端数据产品的设计思路
  18. Google Groups
  19. R语言 - 安装R及RStudio(Linux、Windows双重记录)
  20. linux查看系统日志命令

热门文章

  1. 企业级反向代理 Haproxy
  2. 机器学习基础-统计学习-SLT
  3. 分析Padavan的代码一
  4. web前端入门知识大全:系统路线,各类要点解析
  5. windows PC 连接Windows作为无线显示器
  6. fullpage得基本使用
  7. java win7 管理员权限_win7系统获取管理员权限批处理的操作方法
  8. ppt 转html乱码,ppt转换成pdf乱码解决方法.pdf
  9. that's why you go away(song)
  10. 3.注册后台处理逻辑编写