前言
STL的空间配置器,负责STL的空间配置和管理,主要为容器提供内存空间,在生命容器时,其实已经缺省地指定了空间配置器。
//以vector为例:
template <class T, class Alloc = alloc>
class vector{
...
};
vector<int> a;
//实际上为
vector<int, alloc> a;
空间配置器主要代码在:stl_alloc.h,stl_construct.h和stl_uninitialized.h三个文件中,文章最后会给出SGI STL空间配置器相关代码。
两级配置
stl_alloc.h中,定义了两级空间配置器,第一级配置器用于处理超过128byte的内存申请,第一级配置器直接调用malloc()、realloc()和free()三个函数进行内存分配。
// 第一级配置器直接调用 malloc()
static void* allocate(size_t __n)
{
void* __result = malloc(__n);
if (0 == __result) __result = _S_oom_malloc(__n);
return __result;
}
// 第一级配置器直接调用 free()
static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
}
// 第一级配置器直接调用 realloc()
static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);
if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}
可以发现在申请内存时,如果malloc失败,将调用_S_oom_malloc或_S_oom_realloc函数,这两个函数将通过用户定义的__malloc_alloc_oom_handler来处理内存,如果用户未定义这个句柄,将抛出BAD_ALLOC异常。
template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
void (* __my_malloc_handler)();
void* __result;
// 不断尝试释放、配置
for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
(*__my_malloc_handler)(); // 调用处理例程,企图释放内存
__result = malloc(__n); // 再次尝试配置内存
if (__result) return(__result);
}
}
template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{
void (* __my_malloc_handler)();
void* __result;
for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
(*__my_malloc_handler)(); // 调用处理例程,企图释放内存
__result = realloc(__p, __n); // 再次尝试配置内存,扩大内存大小
if (__result) return(__result);
}
}
stl_alloc.h中的第二级空间配置维护了一个内存池和一组freelist,内存池预先申请好内存放在池中,freelist则从内存池中取内存。
freelist是包含16条链表的数组,每条链表分别存放大小为8byte,16byte,24byte,32byte,64byte,…,128byte,当容器申请内存大小为n个byte的内存时,先找到最小的能满足要求的链表,比如申请19byte,则找到24byte对应的那条链表,取链表中第一个节点分配给容器。
可能的情况是此时大小为24byte的这条链表恰巧用完,则此时freelist会向内存池请求内存,内存池如果内存充足,将直接返回长为20个节点的链表给freelist,如果内存不足以返回20个节点,则返回最大能返回的节点个数,如果连一个节点都不能返回,内存池会先将自己剩余的内存插入到大小合适的链表中,然后申请分配堆上的内存,获得内存后再按照前面的策略为freelist补充内存。最尴尬的情况是堆上的内存也不够用了,此时将寻找freelist中具有更多内存的链表中的节点供给容器使用。
freelist每次分配内存,都将整个节点内存分配给容器,这样每次分配和返还内存,只需要移动链表的头节点,保证了分配的速度。
两级空间配置的优点
- 应用程序申请的内存经常是小块的内存,如果直接从堆上分配大量这种小块内存,堆可能被分裂成多块碎片,那么当程序需要申请一块连续的大内存,堆由于碎片化可能无法提供这样连续的大块内存,而通过内存池和freelist对内存的管理,可以避免内存碎片过多的问题;
- 使用malloc从堆上分配内存通常是低效的,malloc需要调用brk()或mmap()系统调用分配内存,这需要用户态和内核态的两次切换,此外,malloc还需要从堆上找到大小合适的内存,这也需要时间消耗,当内存申请很频繁时,这些时间消耗也是程序希望减少的,内存池和freelist通过集中申请堆内存,能有效减少这些时间消耗。
二级空间配置涉及到的代码较多,建议直接获取源码查阅,这里不再列出。
内存与对象管理分离
stl_construct.h中,主要实现了construct和destroy两个函数,空间配置器将内存分配和对象构造分解成两个过程,stl_alloc.h负责内存分配,而construct负责对象的构造。
首先我们需要知道,new一个对象,分为placement new 和operator new,operator new作用是分配内存,而placement new则在内存上构建对象,construct的实现正是借助了placement new。
template <class _T1, class _T2>
inline void construct(_T1* __p, const _T2& __value) {
_Construct(__p, __value);
}
template <class _T1>
inline void construct(_T1* __p) {
_Construct(__p);
}
// 将初值 __value 设定到指针所指的空间上。
template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
new ((void*) __p) _T1(__value); // placement new,调用 _T1::_T1(__value);
}
template <class _T1>
inline void _Construct(_T1* __p) {
new ((void*) __p) _T1();
}
下面是destroy的代码:
template <class _Tp>
inline void destroy(_Tp* __pointer) {
_Destroy(__pointer);
}
template <class _Tp>
inline void _Destroy(_Tp* __pointer) {
__pointer->~_Tp();
}
destroy还可用于析构两个迭代器之间的对象:
template <class _ForwardIterator>
inline void destroy(_ForwardIterator __first, _ForwardIterator __last) {
_Destroy(__first, __last);
}
template <class _ForwardIterator>
inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {
__destroy(__first, __last, __VALUE_TYPE(__first));
}
template <class _ForwardIterator, class _Tp>
inline void __destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*)
{
typedef typename __type_traits<_Tp>::has_trivial_destructor
_Trivial_destructor;
__destroy_aux(__first, __last, _Trivial_destructor());
}
template <class _ForwardIterator>
inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}
template <class _ForwardIterator>
void
__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
{
for ( ; __first != __last; ++__first)
destroy(&*__first);
}
这里析构时会先判断析构的对象是否是has_trivial_destructor,has_trivial_destructor是萃取剂萃取出的一种特性,我们可以理解为萃取器中存放了被萃取对象的属性,具有has_trivial_destructor属性的数据类型的析构函数什么也没有做,因此可以直接析构,否则逐个地析构对象。
操作大块内存
stl_uninitailized.h中提供了填充大块内存的函数uninitialized_fill(),我们来跟踪一下函数的调用过程:
template <class _ForwardIter, class _Tp>
inline void uninitialized_fill(_ForwardIter __first,
_ForwardIter __last,
const _Tp& __x)
{
__uninitialized_fill(__first, __last, __x, __VALUE_TYPE(__first));
}
template <class _ForwardIter, class _Tp, class _Tp1>
inline void __uninitialized_fill(_ForwardIter __first,
_ForwardIter __last, const _Tp& __x, _Tp1*)
{
typedef typename __type_traits<_Tp1>::is_POD_type _Is_POD;
__uninitialized_fill_aux(__first, __last, __x, _Is_POD());
}
template <class _ForwardIter, class _Tp>
void
__uninitialized_fill_aux(_ForwardIter __first, _ForwardIter __last,
const _Tp& __x, __false_type)
{
_ForwardIter __cur = __first;
__STL_TRY {
for ( ; __cur != __last; ++__cur)
_Construct(&*__cur, __x);
}
__STL_UNWIND(_Destroy(__first, __cur));
}
template <class _ForwardIter, class _Tp>
inline void
__uninitialized_fill_aux(_ForwardIter __first, _ForwardIter __last,
const _Tp& __x, __true_type)
{
fill(__first, __last, __x);
}
inline void fill(unsigned char* __first, unsigned char* __last,
const unsigned char& __c) {
unsigned char __tmp = __c;
memset(__first, __tmp, __last - __first);
}
Is_POD是萃取剂萃取的另外一种属性,具有POD属性意味着数据类型是一个标量或传统的C struct类型,这种数据可以被直接传递,对于非POD类型的数据,则需要逐个构造。
除uninitialized_fill()外,还提供了uninitialized_copy()函数用于复制大块内存中的元素,其实现原理与uninitialized_fill()类似。
template <class _InputIter, class _ForwardIter>
inline _ForwardIter
uninitialized_copy(_InputIter __first, _InputIter __last,
_ForwardIter __result)
{
return __uninitialized_copy(__first, __last, __result,
__VALUE_TYPE(__result));
}
inline char* uninitialized_copy(const char* __first, const char* __last,
char* __result) {
memmove(__result, __first, __last - __first);
return __result + (__last - __first);
}
template <class _InputIter, class _ForwardIter, class _Tp>
inline _ForwardIter
__uninitialized_copy(_InputIter __first, _InputIter __last,
_ForwardIter __result, _Tp*)
{
typedef typename __type_traits<_Tp>::is_POD_type _Is_POD;
return __uninitialized_copy_aux(__first, __last, __result, _Is_POD());
}
template <class _InputIter, class _ForwardIter>
inline _ForwardIter
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
_ForwardIter __result,
__true_type)
{
return copy(__first, __last, __result);
}
template <class _InputIter, class _ForwardIter>
_ForwardIter
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
_ForwardIter __result,
__false_type)
{
_ForwardIter __cur = __result;
__STL_TRY {
for ( ; __first != __last; ++__first, ++__cur)
_Construct(&*__cur, *__first);
return __cur;
}
__STL_UNWIND(_Destroy(__result, __cur));
}
可以看到POD类型是STL中重要的性质之一,我们后面将详细介绍萃取剂和POD类型。