SGI STL 的配置器与众不同,也与标准规范不同,其名称是 alloc 而非 allocator ,而且不接受任何参数。
我们所习惯的c++内存配置操作和释放操作如下:
class Foo { ... };
Foo* pf = new Foo; // 配置内存,然后构造对象
delete pf; // 将对象析构,然后释放内存
这其中的new 操作符(new operator)包含两阶段操作:
(1)调用operator new配置内存
(2)调用Foo::Foo( )构造函数构造对象内容。
delete操作符包含两阶段操作:
(1)调用Foo::~Foo( )析构函数将对象析构。
(2)调用operator delete释放内存
注:如果只是进行空间分配操作,那么使用operator new就可以了,就好比C的malloc函数;如果已经分配好了空间,想在上面构造一个对象,那么可以使用placement new。
STL allocator 将这两阶段操作区分开来。内存配置操作由 alloc::allocate() 负责,内存释放操作由 alloc::deallocate() 负责;对象构造操作由 ::construct() 负责,对象析构操作由 ::destroy() 负责。
内存空间的配置/释放与对象内容的构造/析构,分别着落在<stl_alloc.h>
和<stl_construct.h>
这两个文件身上。此外还有一个文件<stl_uninitialized.h>
,这里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,它们也都隶属于STL标准规范。
总结:
STL空间配置器主要分三个文件实现:
(1)<stl_construct.h>
:这里定义了全局函数construct()和destroy(),负责对象的构造和析构。
(2)<stl_alloc.h>
:文件中定义了一、二两级配置器,彼此合作,配置器名为alloc。
(3)<stl_uninitialized.h>
:这里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,他们也都隶属于STL标准规范。
STL提供了五个全局函数用于处理空间,分别为:
1. construct 用于构造;
2. destroy 用于析构;
3. uninitialized_copy(first, last, result) 将[first,last)
范围内的对象复制到result处;
4. uninitiated_fill(first, last, X) 将[first,last)范围内的内
存用对象X的副本填充;
5. uninitiated_fill_n(first, n, X) 将first开始的n个连续的内存
空间用X的副本填充;
简单来说,alloc主要在如下方面超越了allocator
1.通过内存池技术提升了分配效率:
2.对小内存频繁分配所可能造成的内存碎片问题的处理
3.对内存不足时的处理
SGI STL在<stl_alloc.h>
中定义了两级配置器。第一级空间配置器使用malloc/free函数,当分配的空间大小超过128 bytes的时候使用第一级空间配置器;第二级空间配置器使用了内存池技术,当分配的空间大小小于128 bytes的时候,将使用第二级空间配置器。
当用户申请大区块时,它将其交予第一级配置器。当用户申请小区块时,将与内存池打交道,内存池通过自由链表(free-list)来管理小区块,当内存池不足时,会一次性向堆中申请足够大的空间。用户可以通过宏来控制使用哪一级配置器(默认为二级配置器)。
static void* allocate(size_t __n)
{
void* __ret = 0;
// 如果大于128 bytes,则使用第一级空间配置器
if (__n > (size_t) _MAX_BYTES) {
__ret = malloc_alloc::allocate(__n);
}
else {
// 通过大小取得free-list数组下标,随后取得对应节点的指针
// 相当于&_S_free_list[_S_freelist_index(__n)]
_Obj* __STL_VOLATILE* __my_free_list
= _S_free_list + _S_freelist_index(__n);
_Obj* __RESTRICT __result = *__my_free_list;
// 如果没有可用的节点,则通过_S_refill分配新的节点
if (__result == 0)
__ret = _S_refill(_S_round_up(__n));
else {
// 将当前节点移除,并当做结果返回给用户使用
*__my_free_list = __result -> _M_free_list_link;
__ret = __result;
}
}
return __ret;
};
大量分配小块的内存空间会带来问题:一是从运行库的堆管理器中取得的内存(如通过malloc获得),会有一部分空间用于存储管理信息,用于管理各个内存块,导致内存的使用率降低;二是过多的小块内存会带来内存碎片问题;采用合适的内存池技术可以避免这些问题。
配置器除了负责配置,也负责回收。为了管理方便,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数。并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字节的小额区块。当申请小于等于128字节时就会检查对应的free list,如果free-list中有可用的区块,就直接拿来,如果没有,就准备通过refill为对应的free-list 重新填充空间。free-list的节点结构如下:
union obj
{
union obj* free_list_link;
char client_data[1];
};
这里使用union结构,是为了节省空间。
空间的回收,则是把内存重新加入到free-list对应的节点链表上去。
前面提到,如果free-list中没有可用的区块,就通过refill为对应的free-list 重新填充空间。此时refill进行了怎样的操作呢?
默认操作是通过_S_chunk_alloc
从内存池中取得20个新的节点添加到free-list链表中,而如果内存池中的内存不够用,这时候能分多少就分多少节点,返回的相应的节点数。再万一内存池一个节点都提供不了,就给内存池新增空间,如果失败,再抛出bad_alloc异常。
template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
int __nobjs = 20;
// 通过内存池分配内存,第二个参数为传引用方式
char* __chunk = _S_chunk_alloc(__n, __nobjs);
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
int __i;
// 如果只分配了一个节点,那么直接返回给用户就是了
if (1 == __nobjs) return(__chunk);
// 如果分配了不止一个节点,那么多余的我们要放到free-list里面去
__my_free_list = _S_free_list + _S_freelist_index(__n);
/* Build free list in chunk */
__result = (_Obj*)__chunk;
*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
for (__i = 1; ; __i++) {
__current_obj = __next_obj;
__next_obj = (_Obj*)((char*)__next_obj + __n);
if (__nobjs - 1 == __i) {
// 最后一个节点的_M_free_list_link指针指向NULL,并跳出循环
__current_obj -> _M_free_list_link = 0;
break;
} else {
__current_obj -> _M_free_list_link = __next_obj;
}
}
return(__result);
}
通过_S_chunk_alloc
,从内存池中分配空间给free-list。_S_chunk_alloc
的流程总结如下:
1、 内存池有足够大小的空间,则分配申请的空间;
2、 内存池没有足够大小的空间,但是至少还能分配一个节点的空间,则能分多少分多少;
3、 内存池一个节点都腾不出来,向系统的heap申请2倍于要求大小的空间,在
此之间,如果内存池剩余有空间,则放到free-list中去;
4、 如果向heap申请空间失败,那么只能看free-list中更大的节点是否有可
用空间了,有则用之,同时递归调用自身修正__nobjs;
5、 如果free-list也没有可用节点了,那么转向第一级空间配置器申请空间;
6、 再不行,第一级空间配置器就抛出bad_alloc异常。
如果有需求的话,内存池中会不断的通过malloc申请新的内存,最后内存池所拥有的内存也会越来越大,当然最后进程结束的时候,这些内存都会由操作系统收回。
部分参考自http://www.programlife.net/stl-allocator.html