实现分配器需要考虑的问题
- 空闲块的组织方式:如何记录现有的空闲块
- 空闲块的选择:如何选择一个合适的空闲块
- 空闲块的分割:选择了一个合适的空闲块后如何处理空闲块内部的剩余部分
- 空闲块的合并:如何处理一个刚刚被释放的块?
隐式空闲链表
块的结构
块的头部编码了块的大小以及块是否分配,因此空闲块可以通过头部中的大小字段隐含的连接着,我们称这种结构为隐式空闲链表
优缺点
- 简单
- 性能差
任何操作的开销都要求对链表进行搜索,其搜索时间和链表中的空闲块和已分配块的和呈线性相关。
如何选择空闲块
- 首次适配:从头开始搜索选择第一个满足大小的块
- 下一次适配:从上一次查询开始选择第一个的块
- 最佳适配:选择适合请求的最小的块
各有优缺点,需要结合自己程序的分配块的大小区间和每个大小的频率来判断孰优孰劣。
分割
选择一整块或者将一块大小进行切割,如何选择块的策略很好的话,产生部分小的内部碎片也是可以接受的,这个时候就不需要进行分割。如果选择的块很不匹配的话就会产生很大的内部碎片,这个时候选择切割就会更好一些,分割后剩余的块就是一个新的空闲块了。
合并
当分配器释放的块,其邻接的部分正好也是一个空闲块。这个时候就可能产生很多小的,但是无法满足请求的空闲块,也称为假碎片。所以就要求适配器能够合并邻接的空闲块来消除加碎片问题,提高内存的使用率。
带边界标志的合并
回顾一下之前的隐式空闲链表,当我们释放一块之后,我们是没办法知道前面一个块的大小的,同时也是无法知道其是否空闲,因此我需要这样的一个信息,某人提出了一个通用的边界标识技术
在每个块的头部和尾部添加同样的标识信息。这样任何一个块都能找到其相邻的空闲块,合并相邻的块也十分简单,只需要改变块大小就可以了。
因为内存资源比较紧张,而且已分配的块是不需要进行合并的,所以已分配的块不需要尾部,只需要一个状态位就好。但是空闲块还是需要尾部标识的。
分离的空闲链表
一种比较高效的减少分配时间的方法,通常称为分离存储,就是维护多个空闲链表,其中每个块有大致相等的大小。
简单分离存储
每个空闲链表包含大小相等的空闲块,如果用户申请一个指定大小的块,通过检查相应的空闲链表,直接分配第一个空闲块即可,因此分配和是释放块都能达到常数时间。
因为链表中都是大小相等的块,不需要合并,所以不需要脚部信息,一个已分配块的地址也可以通过大小推断出来,所以已分配块的头部也不需要
分离适配
STL的分配器
C语言库中提供的GNU malloc就是采用的这种方式。