为什么说allocator是空间配置器而不是内存配置器呢? 因为空间不一定是内存,空间也可以是磁盘或其他辅助存储介质(可以写一个allocator直接向硬盘取空间)。
SGI STL 配置器与其他配置器不同
于标准规范也不同。
其名称是alloc而非allocator。
不接受任何参数。
写法: vector
ps:虽然SGI STL allocator 未能符合标准规则,但不会给我们带来困扰,因为通常我们使用缺省的空间配置器,很少要指定配置器名称。
不建议使用,因为效率不佳,只把C++的::operator new 和 ::operator delete 做了一层薄薄的包装而已。
new 一个对象时:先用::operator new 配置内存在,调用构造函数构造对象。
delete 对象时: 先将对象析构,在调用::operator delete 释放内存;
为了精密分工,STL allocator将这两个阶段的操作分开。
1)内存配置 alloc::allocate()
2)内存释放 alloc::deallocate()
3)对象构造 ::construct()
4)对象析构 ::destroy()
STL 标准规则规定配置器定义于中,SGI内包含以下两个文件:
#include
1)迭代器遍历释放内存
2)判断元素类别析构函数是否“无关痛痒”(目的提高效率)
3)析构函数(正常释放) 4)析构函数”无关痛痒“(不做任何处理)
考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器
C++ new-hander机制:要求系统在内存配置需求无法满足时,调用一个自己所指定的函数。(即在::operator new 无法创建空间时,在丢出std::bad_alloc异常状态前,会先调用由客端指定的处理历程。)总的来说new-header解决内存不足的做法有特定的模式。( SGI以malloc而非::operator new 来配置内存,所以必须仿真一个set_malloc_header(); )
注:alloc并不接受任何template型别参数
malloc_based allocator比default_alloc速度慢
oom_malloc()和oom_realloc()都有内循环,不断调用“内存不足处理历程”,但是如果“内存不足处理历程”并未被客端设定,这两个函数就会调用_THROW_BAD_ALLOC丢出bad_alloc异常信息,或利用exit(1)终止程序。
设计“内存不足处理历程”是客端的责任。
第二级配置器( _ default_alloc_template):
第二配置器多了一些机制,避免了太多小额区快造成内存碎片,从而造成配置时的额外负担。
1. 第二级配置器做法(次层配置):
1)如果区块够大(超过128bytes)就移交给第一级配置器处理。
2)如果区块小于128bytes,则使用内存池管理。
3)每次配置一大块内存,并维护对应之自由链表(free-list),下次若再有相同大小的内存需求,就直接从自由链表中拨出。如果客户端释放归还小额区块,为了方便管理SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(按字节回收),并维护16个自由链表,各自管理大小分别是8,16,24…120,128bytes。(配置器除了负责配置,也负责回收)
无论alloc被定义为第一级还是第二级配置器,SGI都会为它包装一个接口使其符合STL规格(SGI STL容器全部使用这个simple_alloc接口):
当allocate()创建空间发现free_list中没有空间时。就会调用refil()从内存池中取得20个新区块/节点(通过chunk_alloc()完成),当内存池空间不够时可能小于20个。
通过调用chunk_alloc()为free_list增加新区块\节点。
内存池的目的是提供高效的内存分配和释放。
1)通过end_free - start_free 来判断内存池水量,如果水量充足直接调出20个区块返回给free_list。
2)如果内存池足够供应一个以上的区块,却不够20个那么就将这些区块全部拨出去,其pass by reference的nobjs参数将修改为实际拨出的区块数
3)如果一个区块也无法供应,需要利用malloc()从堆(heap)配置内存,新注入的水量大小为需求量的两倍,在加上一个随配置次数增加而愈来愈大的附加量。
比如:配置了40个32bytes的区块,其中第一个拨出给正好需要的,19个交给free_list[3]维护,余的20个留给内存池。当客端调用64bytes区块时,如果free_list[7]为空,内存池有20个32bytes的区块/10也就是10个64bytes的区块,其中一个交给客端,剩下9个留给free_list[7]维护,这时内存池又空了…以此这样循环。这样可以减少频繁扩大内存池的次数,提高内存分配的效率。
⭐️⭐️⭐️注:为什么扩大是两倍量呢?⭐️⭐️⭐️
因为两倍量在一定程度上平衡内存使用效率和内存碎片问题
其实对于内存池扩大倍量没有固定值,根据实际情况可能选择其他倍量更好,只不过通常情况下二倍量是一个较为平衡和经验丰富的选择。