剩下两个是 explicit free list , buddy system,本来打算放在一篇里写,但是可能会太长,所以还是分开好

序言

本系列基本上是对cs:app allocator那一节的总结,除了buddy system会扩展一些

内存分配器的设计空间很大,如:

块的格式:

  • implicit free list: 不显式给出当前块在free list中的前驱,后继,而是将free/allocated block放在一起,每次分配需要时间O(n+m),n是 # free block,m是# allocated block

组织成如下:

为了方便合并(与前驱),一般会使用一个footer,这样可以在O(1)的时间内合并,而不是要扫描整条list,才能得到前驱,footer格式如下

称为boundary tag:前驱的footer就在当前块的前一个字(一个固定的距离)

  • explicit free list

这样一般会在块内指明当前块的前驱后继,这样一般是用于将所有的free block放在一个list上,此时要分配,最坏的情况用时是O(n),n为#free block.

放置策略(free list中块的排序策略)

  • 可以使用FIFO,将新的free block直接放在链表头,此时free可以在O(1)下完成
  • 可以按照地址顺序来排列,其中每个free block的地址都小于后继的地址,缺点是释放时需要搜索free list,找到自己插入的位置,优点是,这种方式搭配first fit,比FIFO搭配first fit对内存的利用率更高,接近best fit

分割策略(split)

malloc时,如果找到一个free block,size大于你想要的,此时可以:

  • 将整个块分配给他,但如果size相差太大,会产生显著的内部碎片
  • 分割:仅分配所需的,将剩下的块继续放入free list

合并策略

任何通用allocator,为了提高内存利用率,都需要合并相邻的空闲块,区别在于合并的时机

  • 一旦可以合并(相邻的都是free block),马上合并,缺点是对于特殊的调用模式,如交叉malloc和free,可能会发生合并分割快速交替进行的抖动现象
  • 推迟合并,如推迟到不能满足当前malloc的请求,就扫描所有的free list,进行合并。根据书上,快速的allocator都会选择某种形式的推迟合并,但是这里将介绍的三种都是选择的立刻合并

K&R allocator

根据上面的分类,kr选择的是:

  1. explict free list
  2. free block 按照地址排序
  3. split时仅分割所请求的
  4. 立刻合并

设计上来说还是比较简单的,但是实现上却并不是那么直观:

分析kr的实现:

块的格式

typedef long Align;union header {struct{union header *ptr;uint size;} s;Align x;
};

虽然是explicit list,但是这里并没有表明前驱,仅使用一个指针指向后继,而且这里使用了union,回顾一下union和struct的区别:

unions provide a way to manipulate different kinds of data in a single area of storage

.. the purpose of union —— a single variable can legitimatelty hold any one of several types                   —— K&R c

但是这里使用union的主要目的是为了对齐,因为当union中有多个数据类型时,他的大小是由最大的数据类型决定的,union中加入Align这个变量,使得这个union所占内存大小永远是Align的整数倍,如struct本身所占是8(指针)+4(unsigned),一共是12,但是为了对齐,分配16(不过似乎这个做法已经不需要了,似乎现在的编译器会自动进行对齐,当然加上也没有错)

另外这里采用的是环形链表,最后一个free block的后继会指向第一个

free

void free(void *ap)
{Header *bp, *p;bp = (Header *)ap - 1;                                    // header本身不是用户直接使用的,因此将用户传入的内存位置前移一个header的大小,就指向了当前block的meta datafor (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr) // 遍历链表,找到自己的位置(按照地址升序组织free list)if (p >= p->s.ptr && (bp > p || bp < p->s.ptr))         // p>=p->s.ptr说明p是最后一个(环形链表)break;                                                // bp > p || bp < p->s.ptr 说明bp应该作为p的后继/***  循环结束有这些可能:* 1.找到了合适的位置,p有后继,bp应该放在p与p的后继之间* 2.p >= p->s.ptr, p是最右边一个,即遍历中没有找到满足bp > p || bp < p->s.ptr的p,bp应该根据地址大小,作为p的前驱或者后继*/if (bp + bp->s.size == p->s.ptr) // 如果bp的结尾和p的后继的开头相邻,那么他们两个应该合并,修改bp的size,因为bp已经和p的后继合并了,所以bp应该指向p的后继的后继{bp->s.size += p->s.ptr->s.size;bp->s.ptr = p->s.ptr->s.ptr;}else // 否则,不需要和p的后继合并,此时可能的情况有:1.p有后继,但是不与bp相邻 2.p无后继,所以bp放在p后面(因为是环形链表,所以即使此时bp应该放在开头,bp的后继也是p原来的后继bp->s.ptr = p->s.ptr;if (p + p->s.size == bp) // p的结尾与bp的开头相邻,那么直接将bp合并进p{p->s.size += bp->s.size;p->s.ptr = bp->s.ptr;}else  // bp不与p合并,说明不相邻,直接让bp作为p的后继p->s.ptr = bp;freep = p;
}

malloc

void *
malloc(uint nbytes)
{Header *p, *prevp;uint nunits;nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1; // 为了让分配的大小,既包含header,也恰好的是header的整数倍if ((prevp = freep) == 0)                                    // 此时没有free block,让头节点指向自己{base.s.ptr = freep = prevp = &base;base.s.size = 0;}// 注意,即使上面的判断不成立,freep也已经赋值给prevp了/*** 如果此时没有free block,那么如上,头节点指向自己,size为0,下面就会调用morecore,请求更多内存并插入到free list,因为这是一个循环* 链表,所以这里一直向前遍历就可以找到free block,找到之后,如果free block的size刚刚好是要申请的,那么直接从free list中删除* 如果大于要申请的,此时将free block的后面(这样就不用修改指针)部分分配出去*/for (p = prevp->s.ptr;; prevp = p, p = p->s.ptr){if (p->s.size >= nunits){if (p->s.size == nunits)prevp->s.ptr = p->s.ptr;else{p->s.size -= nunits;p += p->s.size;     // TODO:不明白这里的指针运算为什么没有scalep->s.size = nunits; // 设置被分配出去block的size(新header)}freep = prevp;return (void *)(p + 1);}if (p == freep)if ((p = morecore(nunits)) == 0)return 0;}
}

morecore

/*** 如果没有free block,那就使用sbrk向os请求更多内存,至少是header的4096倍(为了减少对sbrk的调用)* 然后将os分配的内存转型为header *,设置size,调用free将其插入free list,然后返回freep*/
static Header *
morecore(uint nu)
{char *p;Header *hp;if (nu < 4096)nu = 4096;p = sbrk(nu * sizeof(Header));if (p == (char *)-1)return 0;hp = (Header *)p;hp->s.size = nu;free((void *)(hp + 1));return freep;
}

[动态内存分配] Allocaotrs 1/3 : KR相关推荐

  1. 释放变量所指向的内存_C++动态内存分配(学习笔记:第6章 15)

    动态内存分配[1] 动态申请内存操作符 new new 类型名T(初始化参数列表) 功能: 在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值. 结果值: 成功:T类型的指针,指向 ...

  2. 动态内存分配与柔性数组

    什么时动态内存分配 一般我们写程序都是在栈区分配空间,如果我们想根据需求想随时存放随时释放数据,堆区可以实现根据需求想系统申请所需大小的空间. 建立内存的动态分配 内存的动态分配是通过系统提供的函数来 ...

  3. C++中的动态内存分配

    1.Cpp中的内存分配 了解动态内存在C++中是如何工作的是成为一名合格的C++程序员必不可少的.C++程序中的内存分为两个部分: 栈:在函数内部声明的所有变量都将占用栈内存. 堆:这是程序中未使用的 ...

  4. 【 C 】动态内存分配实用案例(二)之复制字符串

    用动态分配内存制作一个字符串的一份拷贝.注意:调用程序应该负责检查这块内存是否分配成功,这样做允许程序以任何它所希望的方式对错误作出反应. #nclude <stdlib.h> #incl ...

  5. 【 C 】动态内存分配实用案例(一)之读取、排序和打印一列整形值

    什么时候用动态内存分配呢?下面这个案例给出了一个比较实用且精彩地使用动态内存的场合,并且教你如何合理地使用动态内存分配? 动态内存分配一个常见的用途就是为那些长度在运行时才知的数组分配内存空间. 下面 ...

  6. 【 C 】动态内存分配案例分析

    声明一个指向char类型的指针,可以在声明的时候就对其进行初始化,这样是合理的. 例如: E1: #include <stdio.h> #include <stdlib.h> ...

  7. 【C 语言】内存管理 ( 动态内存分配 | 栈 | 堆 | 静态存储区 | 内存布局 | 野指针 )

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  8. 从更底层研究C\C++动态内存分配

    2019独角兽企业重金招聘Python工程师标准>>> 以前在学C++ 的时候,一直不懂:动态内存分配的本质,或者更加深入到底层的意义.虽然说,动态内存分配就是,随机在内存中分配一个 ...

  9. 静态、动态内存分配比较

    首先,在使用动态分配内存技术前,必须明白自己在做什么,这样做与其它的方法有什么不同,特别是会产生哪些负面影响,天下没有免费的午餐.动态分配内存与静态分配内存的区别: 1) 静态内存分配是在编译时完成的 ...

最新文章

  1. 混沌动力学行为研究-分叉图
  2. wireshark合并多个文件_小技巧:快速合并多个excel文件(收藏版)
  3. CodeForces999E 双dfs // 标记覆盖 // tarjan缩点
  4. vsftpd 的配置项目
  5. Objects.equals(a, b)
  6. 论大数据视角下的地球空间信息学的机遇与挑战
  7. 压电式传感器工作原理与应用
  8. 视频教程-2020软考网络规划设计师基础知识视频教程-软考
  9. 【图像超分辨(SR)】通俗直观彻底理解双线性插值、双三次插值及其作用
  10. 二 不插SIM卡的GPRS模组-AIR202通过AT指令链接阿里云
  11. 手机离线地图——基于OruxMaps离线高清卫星地图制作发
  12. 计算机硬盘怎么整理,w7怎么整理磁盘碎片_如何清理win7电脑磁盘碎片
  13. 实验三 mysql数据库与表的创建_实验二 数据库和表的创建与管理
  14. Android的启动流程
  15. NLP自然语言处理-机器学习和自然语言处理介绍(五)
  16. 【Ubuntu】Ubuntu 20.04安装Python3.7
  17. 常用诊疗操作技术题库【1】
  18. 超详细介绍 图像处理(卷积)
  19. @Scheduled(cron = “* * * * * *“) 注解 cron 表达式使用
  20. ssm整合(整合配置)

热门文章

  1. 回文序列是指正读反读均相同的字符序列,如“abba”和“abdba”均是 回文,但“good”不是回文。试写一个算法判定给定的字符串是否为回文序列。
  2. 深入理解CAS算法原理
  3. 维吉尼亚密码破解(Python完整详细源码)
  4. C++算法之归并排序
  5. 均值滤波与中值滤波(python实现)
  6. 【Linux】Rsync基于SSH认证的使用(rsync 命令属于1 v 4 的命令、rsync常用参数基本用法)
  7. CSharp 常用函数
  8. 编译器,解释器,预编译器之间的关系
  9. 【附源码】计算机毕业设计SSM人脸识别考勤系统
  10. Date与tring的转化