STL空间配置器
allocator
value_type
pointer
/const_pointer
reference
/const_reference
size_type
difference_type
allocator::rebind
allocate()
, deallocate()
, construct()
, destroy()
。这4个全是成员函数,作用分别是:配置空间,释放配置空间,构造对象,析构对象。std::allocator
但是不建议用(只是对operator new
和operator delete
做了简单封装)
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()
来负责。
标准规定,配置器定义于头文件
中,
包含两个文件:#include
(这里面定义了一二级配置器)和#include
(这里定义了全局的::construct()
和::destroy()
)。
::construct()
和::destroy()
::construct()
:利用placement new
在传入的地址上构造对象。::destroy()
:有多个版本。1. 接受一个指针类型:直接调用指针指向对象的析构函数;2. 接受两个迭代器,需要先判断类型的析构函数是否trival(无关紧要),如果trival就什么都不做,如果不trival就遍历,依次调用上一个版本的destroy()
。std::alloc
malloc()
和free()
。malloc()
和free()
。malloc_alloc
template<int inst>
class __malloc_alloc_template__{...};
//别名
typedef __malloc_alloc_template__<0> malloc_alloc;
allocate()
直接使用malloc()
。
deallocate()
直接使用free()
。
模拟C++的set_new_handler()
以处理内存不足的状况。
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
,一物二用,每个节点不需要额外的指针来维护链表。
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
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;
}
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);
}
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
异常。