SGI STL空间配置器解析

整理至侯捷的《STL源码剖析》
一般的C++内存配置操作为

class Foo { ... };
Foo *pF=new Foo;
delete pF;

new运算符包含两步操作:

  • 调用 ::operator new 配置内存
  • 调用 Foo 的构造函数构造对象

delete运算符也包含两步操作:

  • 调用 Foo 的析构函数析构对象
  • 调用 ::operator delete 释放内存

STL空间配置器就是完成这两阶段的操作:

  • 内存配置为alloc::allocate()
  • 内存释放为alloc::deallocate()

定义在stl_alloc.h中

  • 对象构造::construct()
  • 对象析构::destroy()

定义在stl_construct.h中

构造和析构

源代码为:

#ifndef __SGI_STL_INTERNAL_CONSTRUCT_H
#define __SGI_STL_INTERNAL_CONSTRUCT_H

#include <new.h>

__STL_BEGIN_NAMESPACE

// construct and destroy.  These functions are not part of the C++ standard,
// and are provided for backward compatibility with the HP STL.  We also
// provide internal names _Construct and _Destroy that can be used within
// the library, so that standard-conforming pieces don't have to rely on
// non-standard extensions.

// Internal names

template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
  new ((void*) __p) _T1(__value); //placement new,在__p指向的内存空间上构造对象,调用有参构造函数
}

template <class _T1>
inline void _Construct(_T1* __p) {
  new ((void*) __p) _T1(); //placement new,在__p指向的内存空间上构造对象,调用无参构造函数
}

template <class _Tp>
inline void _Destroy(_Tp* __pointer) {
  __pointer->~_Tp(); //接受一个指针操作的析构函数版本,调用指针所指对象的析构函数
}

template <class _ForwardIterator>
void
__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
{
  for ( ; __first != __last; ++__first)
    destroy(&*__first);
}

template <class _ForwardIterator> 
inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}

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(_ForwardIterator __first, _ForwardIterator __last) {
  __destroy(__first, __last, __VALUE_TYPE(__first));
}

//针对接受内置基本类型指针参数的特化版本,什么也不做
inline void _Destroy(char*, char*) {}
inline void _Destroy(int*, int*) {}
inline void _Destroy(long*, long*) {}
inline void _Destroy(float*, float*) {}
inline void _Destroy(double*, double*) {}
#ifdef __STL_HAS_WCHAR_T
inline void _Destroy(wchar_t*, wchar_t*) {}
#endif /* __STL_HAS_WCHAR_T */

// --------------------------------------------------
// Old names from the HP STL.

//包装内部函数,对外提供接口
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);
}

template <class _Tp>
inline void destroy(_Tp* __pointer) {
  _Destroy(__pointer);
}

template <class _ForwardIterator>
inline void destroy(_ForwardIterator __first, _ForwardIterator __last) {
  _Destroy(__first, __last);
}

__STL_END_NAMESPACE

#endif /* __SGI_STL_INTERNAL_CONSTRUCT_H */

空间的配置与释放

SGI STL的设计要求是:

  • 向系统堆中申请空间
  • 考虑多线程
  • 考虑内存不足时的处理
  • 考虑过多申请小型区块造成的内存碎片的问题。

下面分析不考虑多线程处理。
考虑内存碎片的问题,SGI STL设计了双层级配置器。第一级配置器直接使用malloc()和free()函数,并且考虑内存不足时的处理;第二级配置器策略为:当配置区块超过128字节时,调用第一级配置器,当小于128字节时,采用内存池的方式。我们可以配置使用哪种方式,默认采用的是第二级配置器。

第一级配置器

第一级配置器采用模板类__malloc_alloc_template实现,静态成员函数allocate实现分配内存,reallocate函数实现重分配内存,deallocate函数实现重新分配内存。

  • deallocate函数比较简单,直接free掉指针指向的内存空间就行。
  • allocate函数首先调用malloc分配指定字节的内存,如果malloc调用失败,没有可用空间内存,则指向out of memory处理函数_S_oom_malloc。在这个函数中是一个for循环,不断的尝试释放、配置的过程,首先如果我们没有定义内存不足处理函数__malloc_alloc_oom_handler(函数指针,默认情况下值为0),那么就会抛出异常__THROW_BAD_ALLOC,一般需要我们自己定义内存不足处理函数,通过函数static void (* __set_malloc_handler(void (*__f)()))()(该函数是参数和返回值都是函数指针的写法,typedef void(*pFun)(),pFun __set_malloc_handler(pFun p)),我们可以将我们自己实现的handler覆盖__malloc_alloc_oom_handler,当有足够的空间可用的时候然后就返回。
  • reallocate函数类似allocate
    源码:
template <int __inst>
class __malloc_alloc_template {

private:

  static void* _S_oom_malloc(size_t);
  static void* _S_oom_realloc(void*, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
  static void (* __malloc_alloc_oom_handler)();
#endif

public:

  static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);
    if (0 == __result) __result = _S_oom_malloc(__n);
    return __result;
  }

  static void deallocate(void* __p, size_t /* __n */)
  {
    free(__p);
  }

  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;
  }

/*
    设置自己实现的内存不足处理函数,通过函数指针传参的方法,返回之前的内存不足处理函数指针,该函数的参数和返回值都是函数指针,typedef void (*pFun)()
*/
  static void (* __set_malloc_handler(void (*__f)()))()
  {
    void (* __old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = __f;
    return(__old);
  }

};

// malloc_alloc out-of-memory handling

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int __inst>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
#endif

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);
    }
}
第二级配置器

我们知道每次申请内存(malloc),都需要额外负担(overhead),操作系统需要来记录我们的申请的内存,这样如果我们每次申请的内存区块都很小,额外负担所占的比例就越大。因此SGI STL中引入了第二级配置器(默认配置器)。具体做法就是如果申请内存大于128字节,就认为区块足够大,转交给第一级配置器,如果小于128字节,就申请一块大的内存,并且将它维护在一个链表中,下次,若有相同大小内存需求,就直接重链表中取,释放了后,然后又回收到链表中。
具体实现定义在模板类__default_alloc_template中。
空间配置涉及到三个函数:

  • allocate函数,首先判断请求的字节数,如果大于128字节,则直接调用第一级配置器,返回请求的内存。如果小于128字节,则在链表中寻找可用的区块,找到则返回,没找到则调用_S_refill重新填充链表,最后进行调整链表的指向。
//链表节点,采用union,一物两用,节省内存开销
union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
/* __n must be > 0      */
  static void* allocate(size_t __n)
  {
    void* __ret = 0;
//大于128字节调用第一级配置器
    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      // Acquire the lock here with a constructor call.
      // This ensures that it is released in exit or during stack
      // unwinding.
#     ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#     endif
      _Obj* __RESTRICT __result = *__my_free_list;
      if (__result == 0) //没找到,重新填充链表
        __ret = _S_refill(_S_round_up(__n));
      else { //找到了,调整链表指向
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };
  • _S_refill函数,首先调用_S_chunk_alloc函数在内存池中申请区块,默认准备获得20个区块,如果只获得了1个区块,这将这个区块分配给申请者,否则准备调整链表,插入新节点。
/* Returns an object of size __n, and optionally adds to size __n free list.*/
/* We assume that __n is properly aligned.                                */
/* We hold the allocation lock.                                         */
template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
    int __nobjs = 20; //申请20个区块,实际得到的可能小于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); //只申请到一个区块时,直接返回给申请者
    __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) {
            __current_obj -> _M_free_list_link = 0;
            break;
        } else {
            __current_obj -> _M_free_list_link = __next_obj;
        }
      }
    return(__result);
}
  • _S_chunk_alloc函数,从内存池中申请空间给链表用。首先判断内存池中剩余空间是否满足需求量。如果满足,则返回请求的空间;如果不能满足但有一个以上的区块,则返回最大能供应的区块个数;如果一个区块都不能满足,则首先将剩余的空间编入适当的链表中,然后则从系统堆中申请空间来补充内存池,如果堆申请失败,则进入一个for循环,搜寻适当的链表区块,成功则返回,否则调用第一级配置器。
/* We allocate memory in large chunks in order to avoid fragmenting     */
/* the malloc heap too much.                                            */
/* We assume that size is properly aligned.                             */
/* We hold the allocation lock.                                         */
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, 
                                                            int& __nobjs)
{
    char* __result;
    size_t __total_bytes = __size * __nobjs;
    size_t __bytes_left = _S_end_free - _S_start_free; //内存池剩余字节空间

    if (__bytes_left >= __total_bytes) { //内存池剩余空间满足请求
        __result = _S_start_free;
        _S_start_free += __total_bytes; //调整内存池水位
        return(__result);
    } else if (__bytes_left >= __size) { //内存池剩余空间不能完全满足,但够一个(含)以上区块
        __nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes; //调整内存池水位
        return(__result);
    } else { //内存池中连一个区块都无法提供
        size_t __bytes_to_get = 
      2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        // Try to make use of the left-over piece.
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);

            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        //从堆中申请空间补充内存池
        _S_start_free = (char*)malloc(__bytes_to_get);
        if (0 == _S_start_free) { //堆空间不足
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
        _Obj* __p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                __p = *__my_free_list;
                if (0 != __p) { //free list中有未用区块
                //调整链表,释放出未用区块
                    *__my_free_list = __p -> _M_free_list_link;
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    return(_S_chunk_alloc(__size, __nobjs)); //递归调用自己,修正__nobjs(申请的区块数)
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
        _S_end_free = 0;    // In case of exception.
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); //调用第一级配置器
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get; //调整内存池水位
        return(_S_chunk_alloc(__size, __nobjs)); //递归调用自己,修正__nobjs(申请的区块数)
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值