顾名思义,空间配置器为容器分配数据存储空间,它一般隐藏在容器背后,默默工作。
SGI 空间配置器
SGI STL虽然定义了一个符合STL标准的空间配置器std::allocator
,但是由于该配置器的效率极低,所以SGI STL使用的缺省空间配置器为std::alloc
,它将我们习惯的C++内存配置操作(new关键字)对应的两阶段操作(1、调用::operator new配置内存;2、构造对象内容)区分开来,分别定义于<stl_alloc.h>和<stl_construct.h>两个文件中,由alloc::allocate()和::construct()负责,内存释放操作(delete)做类似处理,由alloc::deallocate()和::destroy()负责。
对象的构造和析构,construct()和destroy()
trivial destructor表示对象使用的是系统默认的析构函数,则无需任何操作。non-trivial destructor则表示对象使用了特定定义的析构函数,这就需要对每个对象一一进行析构。至于如何判断是否为trivial destructor,SGI STL提供了__type_traits技法用于萃取型别的特性。
空间的配置和释放,std::alloc
SGI对此的设计哲学如下:
- 向system heap要求空间
- 考虑多线程状态
- 考虑内存不足时的应变措施
- 考虑过多“小型区块”可能造成的内存碎片问题
双层级配置器
为了更高效地使用空间,SGI设计了双层级配置器,当请求配置的区块超过128bytes时,调用第一级配置器,否则调用第二级配置器。第一级配置器采用malloc()和free()配置和释放内存,而第二级配置器采用复杂的memory pool整理方式。整个设计究竟只开放第一级配置器,还是同时开放第二级配置器,取决于__USE_MALLOC
是否被定义,SGI STL未定义该参数,所以SGI STL默认使用第二级配置器。
第一级配置器
采用malloc()和free()配置和释放内存。当内存不足时,调用客端设计的“内存不足处理例程”,这是模拟的C++ new-handler机制(注意,第一级配置器使用malloc分配内存,所以不能直接使用C++ new-handler机制)。配置器会不断地调用“内存不足处理例程”,直至成功,如果客端没有设定该例程,则丢出bad_alloc异常信息或利用exit(1)硬中止程序。
第二级配置器
第二级配置器维护着16个自由链表,它们各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块,同时,为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数。
自由链表使用union结构,极大节省了内存开销,当区块空着的时候节点被视为一个指向相同形式的另一个节点,而当节点中有数据时,则视为指向实际区块。
union obj{
union obj * free_list_link;
char client_data[1];
}
使用allocate()和deallocate()配置和释放内存,实现过程如图所示:
(1)allocate()
(2)deallocate()
当使用allocate()时发现free list中没有可用区块时,就调用refill(),准备为free list重新填充空间,新空间则取自内存池,该过程由chunk_alloc()完成。缺省取得20个新节点(新区块)。
内存池情况分三种:
(1)内存池剩余空间完全满足需求量,分配所需个数的区块给free list
(2)内存池剩余空间部分满足需求量,只分配相应个数的区块给free list
(3)内存池剩余空间连一个区块的大小都无法提供,将剩余零头分给适当的free list,并从配置堆空间用以补充内存池,如果堆空间也满了,则看看有没有其他free list能用,如果有,就用该区块,如果没有,就山穷水尽,只能再调用一级配置器,看看“内存不足处理例程”有没有什么帮助。
内存基本处理工具
STL定义了5个全局函数,除去之前讨论过的construct()和destroy(),还有uninitialized_copy(),uninitialized_fill()和uninitialized_fill_n(),它们分别对应于高层次函数copy(),fill()和fill_n()。
POD(Plain Old Data),也就是标量型别(scalar types)或传统的C struct型别。该型别必然拥有trivial ctor/dtor/copy/assignment函数,因此,我们可以对POD型别采用最有效率的初值填写手法,而对non-POD型别采取最保险安全的做法。
针对char*和wchar_t*
两种型别,可以采用最具效率的做法memmove(直接移动内存内容)来执行复制行为。
void *memmove(void *str1, const void *str2, size_t n)
- str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
- str2 – 指向要复制的数据源,类型强制转换为 void* 指针。
- n – 要被复制的字节数。