STL(一):allocator

前言

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类型。

SGI STL源码

allocator相关源码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值