STL空间配置器
空间配置器——《STL源码剖析》chapter2
2.1 STL标准规定的空间配置器的标准接口
- STL规定的空间配置器的名称是
allocator
- 包含下列typedef:
value_type
pointer
/const_pointer
reference
/const_reference
size_type
difference_type
- 包含
allocator::rebind
- 包含成员函数:
allocate()
,deallocate()
,construct()
,destroy()
。这4个全是成员函数,作用分别是:配置空间,释放配置空间,构造对象,析构对象。 - SGI实现的STL未遵守STL标准的规定,使用的是一个具有次配置能力(sub-allocation)的特殊配置器。
2.2 具有次配置能力的SGI STL空间配置器
2.2.1 SGI也实现了STL标准规定的std::allocator
但是不建议用(只是对operator new
和operator delete
做了简单封装)
2.2.2 SGI中的特殊配置器名字叫std::alloc
-
class Foo{...}; Foo *pf = new Foo; delete pf;
上面的new算式包含两阶段操作:1. 调用
operator new
配置内存,2. 调用Foo::Foo()
构造函数对象。delete算式也包含两阶段操作:1. 调用
Foo::~Foo()
将对象析构,2. 调用operator delete
释放内存。 -
为了精密分工,STL配置器将这两阶段操作区分开来。内存的配置和释放由
alloc
的成员函数alloc::allocate()
和alloc::deallocate()
来负责,对象的构造和析构由全局函数::construct()
和::destroy()
来负责。 -
标准规定,配置器定义于头文件
<memory>
中,<memory>
包含两个文件:#include<stl_alloc.h>
(这里面定义了一二级配置器)和#include<stl_construct.h>
(这里定义了全局的::construct()
和::destroy()
)。
2.2.3 构造和析构基本工具::construct()
和::destroy()
::construct()
:利用placement new
在传入的地址上构造对象。::destroy()
:有多个版本。1. 接受一个指针类型:直接调用指针指向对象的析构函数;2. 接受两个迭代器,需要先判断类型的析构函数是否trival(无关紧要),如果trival就什么都不做,如果不trival就遍历,依次调用上一个版本的destroy()
。
2.2.4 空间的配置与释放std::alloc
- 考虑小区块造成的内存破碎问题,SGI设计了双层级配置器。
- 第一级:直接使用
malloc()
和free()
。 - 第二级:
- 如果配置区块大小超过128byte,直接用
malloc()
和free()
。 - 如果配置区块大小不足128byte,为降低额外负担,采用复杂的memory pool整理方式。
- 如果配置区块大小超过128byte,直接用
2.2.5 第一级配置器malloc_alloc
-
template<int inst> class __malloc_alloc_template__{...}; //别名 typedef __malloc_alloc_template__<0> malloc_alloc;
-
allocate()
直接使用malloc()
。 -
deallocate()
直接使用free()
。 -
模拟C++的
set_new_handler()
以处理内存不足的状况。
2.2.6 第二级配置器
-
sub-allocation:每次配置一大块内存,并维护对应之自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-lists中拔出。如果客端释还小额区块,就由配置器回收到free-list中。
-
第二级配置器会自动将内存需求量上调至8的倍数,并维护16个free-lists(分别管理8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块)
-
free-list节点的结构
union obj{ union obj * free_list_link; char client_data[1]; };
使用
union
,一物二用,每个节点不需要额外的指针来维护链表。
2.2.7 空间配置函数allocate()
-
源码:
// n must be > 0 static void * allocate(size_t n) { obj * volatile * my_free_list; obj * result; // 大于128就调用第一级配置器 if(n>(size_t)__MAX_BYTES){ return (malloc_alloc::allocate(n)); } //寻找16个free-lists中适当的一个 my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; if(result == 0){ //没有可用free-list,准备重新填充free-list void *r = refill(ROUND_UP(n)); return r; } //调整free-list *my_free_list = result->free_list_link; return (result); }
-
第一步:找打n对应的free-list;第二步:将result指向这个free-list的开头;第三步:调整free-list。二三两步就是将一个区块从free-list中摘下来。
-
如果没有可用的free-list,调用
refill()
重新填充free-list
2.2.8 空间释放函数deallocate()
-
源码:
//p 不可以是0 static void deallocate(void *p, size_t n) { obj *q = (obj*)p; obj *volatile * my_free_list; // 大于128就调用第一级配置器 if (n>(size_t)__MAX_BYTES){ malloc_alloc::deallocate(p,n); return; } my_free_list = free_list + FREELIST_INDEX(n); //调整free-list,回收区块 q->free_list_link = *my_free_list; *my_free_list = q; }
2.2.9 重新填充refill()
-
当free-list没有可用区块的时候,
refill()
从内存池中获取新的空间。 -
缺省获得20个区块大小的空间,若内存池空间不足,实际获取的区块可能不足20。
-
源码
//假设n已经上调至8的倍数 template <bool threads, int inst> void* __default_alloc__template<threads,inst>::refill(size_t n) { int nobjs = 20; //调用chunk_alloc(),尝试获得nobjs个区块作为free-list的新节点 //注意参数nobjs是pass by reference char* chunk = chunk_alloc(n,nobjs); obj * volatile * my_free_list; obj * result; obj * current_obj, * next_obj; int i; //如果只有一个区块,这个区块就分配给调用者用,free-list无新节点 if(n==1) return (chunk); //否则准备调整free-list my_free_list = free_list + FREELIST_INDEX(n); //以下在chunk空间内建立free-list result = (obj*) chunk; //这一块准备返回给客端 //以下引导free-list指向新配置的空间 *my_free_list = next_obj = (obj*)(chunk+n); //以下将free-list的各节点串联起来 for(i=1;;i++){ current_obj = next_obj; next_obj = (obj*)((char*)next_obj+n); if(nobjs-1 == i){ current_obj->free_list_link = 0; break; }else{ current_obj->free_list_link = next_obj; } } return (result); }
2.2.10 内存池
-
chunk_alloc()
从内存池中取空间给free-list使用,在refill()
中被调用。 -
template <bool threads, int inst> char* __default_alloc_template__<threads,inst>:: chunk_alloc(size_t size, int& nobjs) { //··· }
-
chunk_alloc()
的格式如上面所示,根据传进来的size
和nobjs
分配空间。 -
首先在
__default_alloc_template__
类中有两个指针:start_free
和end_free
,两个指针都是static char*
格式,分别指向内存池起始位置和内存池结束位置。 -
chunk_alloc()
以end_free
-start_free
判断内存池的水量,如果水量充足,就直接调出20个区块返回给free-list。 -
如果水量不足以提供20个区块,但还足够提供一个以上的区块,就拨出这不足20个区块的空间出去。此时pass by reference的
nobjs
参数将被修改为实际能提供的区块数。 -
如果内存池连一个区块空间都不够了,就调用
malloc
从heap中配置内存,为内存池注入源头活水以应付需求。 -
万一山穷水尽,整个system heap空间都不够了,
malloc
失败,chunk_alloc
就四处寻找有无“尚有未用区块,且区块足够大”之free-list。找到了就挖一块交出,找不到就调用第一级配置器(寄希望于第一级配置器的out-of-memory处理机制)。如果可以就成功,否则就抛出bad_alloc
异常。