空间配置器是最不需要介绍的东西,他总是隐藏在容器(container)的背后,默默工作,默默付出。
allocator 之所以说是空间配置器而不是内存配置器,因为空间不一定是内存,也可以是磁盘或者其它辅助存储介质。
空间配置器的标准接口
allocator::allocator()
default constructor
allocator: :allocator (const allocator&)
copy constructor
template <class Uallocator:allocato (const allocator<U>&)
泛化的copy constructor
allocator: :~allocator ()
destructor
pointer allocator: :address (reference x) const
返回某个对象的地址。算式a.address(x)等同于&x
const_ pointer allocator: : address (const_ reference x) const
返回某个const对象的地址。算式a. address(x)等同于&x
pointer allocator: :allocate(size_ type n, cosntvoid* = 0)
配置空间,足以存储n个T对象。第二参数是个提示,实现上可能会利用它来增进区域性(locality) ,或完全忽略之
void allocator:;deallocate(pointer P, size. _type n)
归还先前配置的空间
size_ type allocator: max Bize() const
返回可成功配置的最大量
void allocator: :construct (pointer P, const T&x)
等同于new((void*) p) T(x)
void allocator::destroy(pointer p)
等同于p->~T()
具有次配置力的 SGI 空间配置器
在程序中采用 SGI 配置器:
vector<int,std::alloc> iv; //in GCC
在 SGI 的每一个容器都已经指定其缺省的空间配置器为 alloc
因为SGI中标准的空间配置器,std::allocator 只是对 C++ 中的 ::operator new 和 ::operator delete 做一层薄薄的封装,所以在这里主要了解 SGI 特殊的空间配置器,std::alloc
1.SGI 特殊的空间配置器,std::alloc
一般而言,我们所习惯的C++内存配置操作和释放操作是这样的:
class Foo {...}
Foo* pf = new Foo; //配置内存,然后构造对象
delete pf; //将对象析构,然后释放内存。
其中,new算式包含两阶段操作:
(1) 调用 ::operator new 配置内存
(2) 调用 Foo::Foo() 构造对象内容
delete 算式也包含两阶段操作:
(1) 调用 Foo::~Foo() 将对象析构
(2) 调用 ::operator delete 释放内存
内存配置操作由 alloc::allocate() 负责,内存释放操作由 alloc::deallocate() 负责;
对象构造操作由 ::construct() 负责,对象析构操作由 ::destroy() 负责。
2.构造和析构基本工具: construct() 和 destroy()
inline void construct(T1* p,const T2& value){
new (p) T1(value); //调用T1::T1(value)
}
inline void destroy(T* pointer){
pointer->~T(); //调用dtor ~T
}
inline void destroy(ForwardIterator first,ForwardIterator last){
_destroy(first,last,value_type(first));
}
这两个作为构造、析构之用的函数被设计成全局函数。
上述 construct() 接收一个指针 p 和一个初值 value,该函数的用途就是将初值设定到指针所指的空间上。
destroy() 有两个版本,第一版本接受一个指针,准备将该指针所指之物析构掉;第二版本接受 first,last 两个迭代器,将 [first,last) 范围内的所有对象析构掉。
3.空间的配置与释放,std::alloc
C++ 的内存配置基本操作是 ::operator new() ,内存释放基本操作是 ::operator delete()。相当于C的 malloc() 和 free() 函数。SGI 正是以 malloc() 和 free() 完成内存的配置与释放。
考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器,第一层配置器直接使用 malloc() 和 free() ,第二级配置器视情况采用不同的策略:当配置区块超过 128bytes时,视之为"足够大",调用第一级配置器;当配置区块小于 128bytes时,视之为"过小",为了降低额外负担,采用复杂的 memory pool 整理方式(内存池),而不再求助于第一级配置器。
4.第一级配置器 __malloc_alloc_template 剖析
第一级配置器以 malloc()、free()、realloc() 等 C函数执行实际的内存配置、释放、重配置等操作,并实现出类似C++ new-handler的机制。
C++ new hanlder 机制是,你可以要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。
5.第二级配置器 __default_alloc_template 剖析
第二级配置器多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的其实不仅是内存碎片,配置时的额外负担也是一个大问题。
SGI 第二级配置器的做法是,如果区块够大,超过 128bytes,就移交第一级配置器处理。当区块小于 128bytes时,则以内存池 memory pool 管理,此法又称为 sub-allocation 次层配置:每次配置一大块内存,并维护对应之自由链表 free list。下次若再有相同大小的内存需求,就直接从 free-lists 中拨出。如果客户端释还小额区块,就由配置器回收到 free-lists中。
SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(30bytes->32bytes),并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块。
6.内存池
上述的 chunk_ alloc() 函数以 end free 一start free 来判断内存池的水量.如果水量充足,就直接调出20个区块返回给free list.如果水量不足以提供20个区块,但还足够供应一个以上的区块, 就拨出这不足20个区块的空间出去,这时候其pass by reference的nobjs参数将被修改为实际能够供应的区块数、如果内存池连一个区块空间都无法供应,对客端显然无法交待:此时便需利用malloc()从heap 中配置内存,为内存池注人源头活水以应付需求。新水量的大小为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
举个例子,见图2-7, 假设程序开始, 客端就调用chunk_ alloc(32,20),于是malloc()配置40个32bytes区块,其中第1个交出,另19个交给free_ list[3]维护,余20个留给内存池,接下来客端调用chunk_ alloc(64,20), 此时free_ list[7] 空空如也,必须向内存池要求支持。内存池只够供应(32*20)/64=10个64 bytes区块,就把这10个区块返回,第1个交给客端,余9个由free_ list[7]维护。此时内存池全空。接下来再调用chunk alloc(96, 20),此时free_ list[11]室空如也, 必须向内存池要求支持,而内存池此时也是空的,于是以malloc()配置40+n (附加量)个96 bytes区块,其中第1个交出,另19个交给free_ list1]维护,余20+n (附加量)个区块留给内存池…
万一山穷水尽,整个system heap空间都不够了( 以至无法为内存池注人源头活水) ,malloc() 行动失败,chunk _alloc()就四处寻找有无“尚有未用区块,且区块够大”之free-lists找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc() 来配置内存,但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其它的内存拿来此处使用。如果可以,就成功,否则发出badalloc 异常。