STL源码剖析 - 第2章 空间配置器

 SGI STL源码下载地址

    空间配置是为存储数据提供可用的空间,在Standard Template Library(STL)中,空间配置是最底层的东西,为容器提供服务。

2.1 空间配置器的标准接口

   空间配置器(Allocator)的主要实现文件是alloc.h和stl_alloc.h,标准接口位于文件stl_alloc.h的588-628行;具体如下:

/*tihs program is in the file of stl_alloc.h from line 588 to 628 */  
  
template <class _Tp>  
class allocator {  
  typedef alloc _Alloc;          // The underlying allocator.  
public:                         //数据类型的成员变量在后续章节(traits编程技巧)介绍  
  typedef size_t     size_type;  
  typedef ptrdiff_t  difference_type;  
  typedef _Tp*       pointer;  
  typedef const _Tp* const_pointer;  
  typedef _Tp&       reference;  
  typedef const _Tp& const_reference;  
  typedef _Tp        value_type;  
  
  template <class _Tp1> struct rebind {//嵌套一个template,且仅包含唯一成员other,是一个typedef;  
    typedef allocator<_Tp1> other;  
  };  
  //下面是成员函数  
  allocator() __STL_NOTHROW {}  //默认构造函数,__STL_NOTHROW在 stl_config.h中定义,要么为空,要么为 throw()异常机制  
  allocator(const allocator&) __STL_NOTHROW {}  //复制构造函数  
  template <class _Tp1> allocator(const allocator<_Tp1>&) __STL_NOTHROW {}//泛化的复制构造函数  
  ~allocator() __STL_NOTHROW {}//析构函数  
  
  pointer address(reference __x) const { return &__x; }//返回对象的地址  
  const_pointer address(const_reference __x) const { return &__x; }//返回const对象的地址  
  
  // __n is permitted to be 0.  The C++ standard says nothing about what  
  // the return value is when __n == 0.  
  _Tp* allocate(size_type __n, const void* = 0) {// 配置空间,如果申请的空间块数不为0,那么调用 _Alloc 也即 alloc 的 allocate 函数来分配内存,  
 //这里的 alloc 在 SGI STL 中默认使用的是__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>这个实现(见第402行)  
    return __n != 0 ? static_cast<_Tp*>(_Alloc::allocate(__n * sizeof(_Tp)))   
                    : 0;  
  }  
  
  // __p is not permitted to be a null pointer.  
  void deallocate(pointer __p, size_type __n)//释放已配置的空间  
    { _Alloc::deallocate(__p, __n * sizeof(_Tp)); }  
  
  size_type max_size() const __STL_NOTHROW //返回可成功配置的最大值  
    { return size_t(-1) / sizeof(_Tp); }  
  
  void construct(pointer __p, const _Tp& __val) { new(__p) _Tp(__val); }//构造,等同于new ((void*)p) T(x)  
  void destroy(pointer __p) { __p->~_Tp(); }//析构,等同于p->~T()  
};  


2.2 具备次配置力的SGI空间配置器

SGI STL底层没有采用STL标准规范中的声明,它的配置器与众不同,其名称是alloc而非allocator,而且不接受任何参数。

SGI STL中的每种容器都已经指定其缺省的空间配置器alloc,我们在使用时很少需要自行指定配置器。

SGI STL空间配置器的设计哲学:

1.        从堆中申请内存空间

2.        考虑多线程状态

3.        考虑内存不足时的应变措施

4.        考虑过多“小型区块”可能造成的内存碎片问题

SGI STL空间配置器alloc的设计描述:

使用双层级配置器

第一级配置器(__malloc_alloc_template)直接使用malloc() 和free() 

第二级配置器(__default_alloc_template)视情况采用不同策略:当配置区块超过128byte时,视之为“足够大”,便调用第一级配置器;否则,视之为“过小”,为了提高效率(1.减少内存碎片;2.降低额外负担:直接使用malloc()申请内存时,系统会要多分配一些内存(内存头)用来管理所分配的内存空间),采用复杂的内存池(memory pool)技术(见下文详解),不再求助于第一级配置器。

2.2.2 SGI特殊的空间配置器,std::alloc

   在C++中,一般管理内存分配是使用new和delete进行操作,这两个操作都需要经过两个步骤;

new操作的步骤:

(1)调用::operator new配置内存;

(2)调用对象的构造函数构造对象并初始化。

delete操作步骤:

(1)调用对象的析构函数析构对象;

(2)调用::operator delete释放内存。

例如:

class Foo { ... };  

Foo* pf = new Foo;  

...  

delete pf;  

    而在STL中,空间配置在C++的基础上增加了一些特性。STLallocator 将这两个阶段分开操作,内存配置操作由空间配置器stl::alloc中的alloc::allocate(),内存释放由alloc::deallocate()负责;对象构造操作由::construct()负责,对象析构操作由::destroy()负责。

 

在SGI STL的stl_alloc.h文件中,可以看到有以下几种空间配置的类模板:

template <int __inst> class __malloc_alloc_template   
// Malloc-based allocator.  Typically slower than default alloc  
typedef __malloc_alloc_template<0> malloc_alloc  
template<class _Tp, class _Alloc> class simple_alloc  
template <class _Alloc> class debug_alloc  
template <bool threads, int inst> class __default_alloc_template  
 // Default node allocator.  
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc  
typedef __default_alloc_template<false, 0> single_client_alloc  
template <class _Tp>class allocator  
template<>class allocator<void>  
template <class _Tp, class _Alloc>struct __allocator  
template <class _Alloc>class __allocator<void, _Alloc>  

   其中simple_alloc,debug_alloc,allocator和__allocator都是对其他适配器(如__Alloc::allocate)的一个 简单封装。__malloc_alloc_template和__default_alloc_template这两个配置器就是SGISTL配置器的重点。其中__malloc_alloc_template是SGI STL的第一级配置器,只是对系统的malloc,realloc,free函数的一个简单封装,并考虑到了分配失败后的异常处理。而__default_alloc_template是SGI STL的第二级配置器, 在第一级配置器的基础上还考虑了内存碎片的问题,通过内置一个轻量级的内存池,及在多线程环境下内存池互斥访问的机制。

2.2.5 第一级配置器__malloc_alloc_template:异常处理

    第一级配置器内存分配失败一般是由于内存不足out-of-memory(oom),处理异常时,首先用户自己设计异常处理例程,若用户没有定义,仅仅是打印错误信息并强制退出。总的来说,就是留给用户异常处理接口和默认强制退出处理。

//异常处理  
/*tihs program is in the file of stl_alloc.h*/  
//line 109 to 118  
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  
  //line 141 to 146  
  //指定自己的异常处理  
  static void (* __set_malloc_handler(void (*__f)()))()  
  {  
    void (* __old)() = __malloc_alloc_oom_handler;  
    __malloc_alloc_oom_handler = __f;  
    return(__old);  
  }  
//line 152 to 155  
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG  
template <int __inst>  
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;  
#endif  
//line 41 to 50  
#ifndef __THROW_BAD_ALLOC  
#  if defined(__STL_NO_BAD_ALLOC) || !defined(__STL_USE_EXCEPTIONS)  
#    include <stdio.h>  
#    include <stdlib.h>  
//默认的强制退出  
#    define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)  
#  else /* Standard conforming out-of-memory handling */  
#    include <new>  
//抛出用户设计异常处理例程  
#    define __THROW_BAD_ALLOC throw std::bad_alloc()  
#  endif  
#endif  


2.2.6 第二级配置器__default_alloc_template

    第二级配置器主要是利用内存池进行管理小内存分配问题,并且在多线程环境下内存池的互斥访问问题。第一级配置器__malloc_alloc_template只是malloc的一层封装,没有考虑内存碎片问题。因此,第二级配置器是在第一级配置器的基础上考虑了内存碎片问题,对于申请内存大于128bytes移交给第一级配置器__malloc_alloc_template处理。对于小内存(小于128bytes)的申请,利用内存池来管理,直接从内存池分配即可,并维护自由链表,自由链表是来分配同样的小内存和回收小内存,即程序再次申请小内存直接从自由链表中分配,当小内存释放时,自由链表对其进行回收。

    为了方便管理,SGISTL第二级配置器会主动将任何小额区块的内存需求量上调为8的倍数,即若用户申请的小额区块内存不满足8的倍数时,系统自动向上取整为8的倍数。由于SGI STL第二级配置器要求小额区块的内存最大为128bytes,则自由链表的个数为16个,即128/8=16;每个链表分别维护区块内存大小为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes。

第二级配置器处理的源代码:

/*tihs program is in the file of stl_alloc.h from line 288 to 375 */  
//第二级配置器__default_alloc_template  
template <bool threads, int inst>  
class __default_alloc_template {  
  
private:  
  // Really we should use static const int x = N  
  // instead of enum { x = N }, but few compilers accept the former.  
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__))  
    enum {_ALIGN = 8};//小额区块的上调边界  
    enum {_MAX_BYTES = 128};//小额区块的最大内存  
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN;自由链表个数  
# endif  
  static size_t  
  _S_round_up(size_t __bytes) //函数功能:调整内存大小为8的倍数  
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }  
  
__PRIVATE:  
  union _Obj {//自由链表节点属性  
        union _Obj* _M_free_list_link;  
        char _M_client_data[1];    /* The client sees this.        */  
  };  
private:  
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)  
    static _Obj* __STL_VOLATILE _S_free_list[];   
        // Specifying a size results in duplicate def for 4.1  
# else  
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];   
# endif  
  static  size_t _S_freelist_index(size_t __bytes) {//函数功能:计算所申请区块内存在自由链表中对应的号数,从0开始  
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);  
  }  
  
  // Returns an object of size __n, and optionally adds to size __n free list.  
  static void* _S_refill(size_t __n);//填充空间,把大小为n的内存空间加到自由链表  
  // Allocates a chunk for nobjs of size size.  nobjs may be reduced  
  // if it is inconvenient to allocate the requested number.  
  /*从内存池中分配空间,该空间可容纳__nobjs大小为__size的区块,可能会少于__nobjs个*/  
  static char* _S_chunk_alloc(size_t __size, int& __nobjs);  
  
  // Chunk allocation state.  
  static char* _S_start_free;//内存池起始位置  
  static char* _S_end_free;//内存池结束位置  
  static size_t _S_heap_size;  
  
# ifdef __STL_THREADS  
    static _STL_mutex_lock _S_node_allocator_lock;  
# endif  
  
    // It would be nice to use _STL_auto_lock here.  But we  
    // don't need the NULL check.  And we do need a test whether  
    // threads have actually been started.  
    class _Lock;  
    friend class _Lock;  
    class _Lock {//该类保证内存池在多线程环境解决互斥访问  
        public:  
            _Lock() { __NODE_ALLOCATOR_LOCK; }  
            ~_Lock() { __NODE_ALLOCATOR_UNLOCK; }  
    };  
    public:  
  
  /* __n must be > 0      */  
  static void* allocate(size_t __n)  
  {  
    void* __ret = 0;  
  
    if (__n > (size_t) _MAX_BYTES) {  
      __ret = malloc_alloc::allocate(__n);//内存大于128时,采用第一级配置器处理  
    }  
    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;  
  };  
//初始化操作  
//line from 554 to 571  
  template <bool __threads, int __inst>  
char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;  
  
template <bool __threads, int __inst>  
char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;  
  
template <bool __threads, int __inst>  
size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;  
  
template <bool __threads, int __inst>  
typename __default_alloc_template<__threads, __inst>::_Obj* __STL_VOLATILE  
__default_alloc_template<__threads, __inst> ::_S_free_list[  
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)  
    _NFREELISTS  
# else  
    __default_alloc_template<__threads, __inst>::_NFREELISTS  
# endif  
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };  


2.2.7 空间配置函数allocate()

  空间配置函数allocate()的具体实现步骤如下:

1、若用户申请的内存大于128bytes,则调用第一级配置器分配空间;

2、若小于128bytes检查对应的自由链表free_list,如果自由链表存在可用的区块,则直接使用,若不存在,则调用填充函数refill()为自由链表重新填充空间;

  空间配置函数allocate()的源代码如下:

 /* __n must be > 0      */  
  static void* allocate(size_t __n)  
  {  
    void* __ret = 0;  
  
    if (__n > (size_t) _MAX_BYTES) {  
      __ret = malloc_alloc::allocate(__n);//内存大于128时,采用第一级配置器处理  
    }  
    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)//若自由链表free_list不存在可用的区块,则从内存池中填充自由链表  
        __ret = _S_refill(_S_round_up(__n));  
      else {//若自由链表free_list存在可用区块,调整free_list  
        *__my_free_list = __result -> _M_free_list_link;  
        __ret = __result;  
      }  
    }  
  
    return __ret;  
  };  


2.2.8 空间释放函数deallocate()

    首先判断区块的大小,大于128bytes直接调用第一级配置器,若小于128bytes,则找出相应的自由链表free_list,将其回收。源代码如下:

  /* __p may not be 0 */  
  static void deallocate(void* __p, size_t __n)  
  {  
    if (__n > (size_t) _MAX_BYTES)//内存大于128时,采用第一级配置器处理  
      malloc_alloc::deallocate(__p, __n);  
    else {//否则,找到相应的自由链表位置,将其回收  
      _Obj* __STL_VOLATILE*  __my_free_list  
          = _S_free_list + _S_freelist_index(__n);  
      _Obj* __q = (_Obj*)__p;  
  
      // acquire lock  
#       ifndef _NOTHREADS  
      /*REFERENCED*/  
      _Lock __lock_instance;  
#       endif /* _NOTHREADS */  
      __q -> _M_free_list_link = *__my_free_list;  
      *__my_free_list = __q;  
      // lock is released here  
    }  
  }  


2.2.9 重新填充函数refill()

    重新填充函数refill()是在自由链表不存在可用的区块时被调用。默认是为自由链表申请20个节点,第1个给客户端,剩下19个留给自由链表管理。源代码如下:

/* 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;//默认节点数  
    //调用_S_chunk_alloc,从内存池中获得内存空间  
    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;//这一块返回给客户端  
      //自由链表free_list指向新配置的空间  
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);  
      for (__i = 1; ; __i++) {//这里第0个返回给客户端,所以从1开始  
        __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);  
}  


2.2.10 内存池管理机制

     chunk_alloc函数具体实现步骤如下:

1.        内存池剩余空间完全满足20个区块的需求量,则直接获取对应大小的空间。

2.        内存池剩余空间不能完全满足20个区块的需求量,但是足够供应一个及以上的区块,则获取满足条件的区块个数的空间。

3.        内存池剩余空间不能满足一个区块的大小,则:

Ø  首先判断内存池中是否有残余零头内存空间,如果有则进行回收,将其编入free_list。

Ø  然后向heap申请空间,补充内存池。

²  heap有足够的空间,空间分配成功。

²  heap空间不足,即malloc()调用失败。则

ü  查找free_list中尚有未用区块,调整以进行释放,将其编入内存池。然后递归调用chunk_alloc函数从内存池取空间供free_list备用。

ü  搜寻free_list释放空间也未能解决问题,这时候调用第一级配置器,利用out-of-memory机制尝试解决内存不足问题。

   源代码如下:

/* 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) {//若内存池剩余空间满足20个需求,直接分配  
        __result = _S_start_free;  
        _S_start_free += __total_bytes;  
        return(__result);  
    } else if (__bytes_left >= __size) {  
        /*若内存池剩余空间不满足20个需求,但足够满足一个或多个,取出能够满足条件区块的个数*/  
        __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) {  
            /*判断内存池中是否有残余零头内存空间,如果有则进行回收,将其编入free list*/  
            _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) {  
    /*搜寻适当的free list(适当的是指:尚有未用区块,并且区块足够大),调整以进行释放,将其编入内存池。 
                     **然后递归调用chunk_alloc函数从内存池取空间供free list。*/  
                __my_free_list = _S_free_list + _S_freelist_index(__i);  
                __p = *__my_free_list;  
                if (0 != __p) {//自由链表中存在未被使用的区块,调整并释放该区块  
                    *__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));  
                    // 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));  
    }  
}  


总结一下:

      1、STL空间配置器:主要分三个文件实现,stl_construct.h  这里定义了全局函数construct()和destroy(),负责对象的构造和析构。stl_alloc.h文件中定义了一、二两级配置器,彼此合作,配置器名为alloc。stl_uninitialized.h 这里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,他们也都隶属于STL标准规划。
      在stl_alloc.h中定义了两级配置器,主要思想是申请大块内存池,小块内存直接从内存池中申请,当不够用时再申请新的内存池,还有就是大块内存直接申请。当申请空间大于128字节时调用第一级配置器,第一级配置器没有用operator::new和operator::delete来申请空间,而是直接调用malloc/free和realloc,并且实现了类似c++中new-handler的机制。所谓c++ new handler机制是,你可以要求系统在内存配置需求无法被满足时,调用一个指定的函数。换句话说,一旦::operator::new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程,该处理例程通常称为new-handler.new-handler解决内存做法有特定的模式。SGI第一级配置器的allocate()和realloc都是在调用malloc和realloc不成功后,改调用oom_malloc()和oom_realloc(),后两者都有内循环,不断调用"内存不足处理例程",期望在某次调用之后,获得足够的内存而圆满完成任务。但如果“内存不足处理例程“并未被客端设定,oom_malloc()和oom_realloc便调用_THROW_BAD_ALLOC, 丢出bad_alloc异常信息,或利用exit(1)硬生生中止程序。
     在stl_alloc.h中定义的第二级配置器中,如果区块够大,超过128字节时,就移交给第一级配置器处理。当区块小于128字节时,则以内存池管理,此法又称为次层配置,每次配置一大块内存,并维护对应的自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中拔出。如果客端释还小额区块,就由配置器回收到free-lists中,另外,配置器除了负责配置,也负责回收。为了管理方便,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中有可用的区块,就直接拿来,如果没有,就准备为对应的free-list 重新填充空间。新的空间将取自内存池,缺省取得20个新节点,如果内存池不足(还足以一个以上的节点),就返回的相应的节点数.如果当内存池中连一个节点大小都不够时,就申请新的内存池,大小为2*total_bytes+ROUND_UP(heap_size>>4),totoal_bytes为申请的空间大小,ROUND_UP调整为8的倍数,heap_size为当前总申请内存池的大小。如果申请该内存池成功就把原来内存池中剩下的空间分配给适当的free-list.万一山穷水尽,整个system heap空间都不够了(以至无法为内存池注入源头活水),malloc()行动失败,就会四处寻找有无"尚有未用区块,且区块足够大 "之free lists.找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc来配置内存。但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其他的内存拿来此处使用。如果可以就成功,否则发出bad_alloc异常。
      2、STL的默认内存分配器
      隐藏在这些容器后的内存管理工作是通过STL提供的一个默认的allocator实现的。当然,用户也可以定制自己的allocator,只要实现allocator模板所定义的接口方法即可,然后通过将自定义的allocator作为模板参数传递给STL容器,创建一个使用自定义allocator的STL容器对象,如:
    stl::vector<int, UserDefinedAllocator> array;
      大多数情况下,STL默认的allocator就已经足够了。这个allocator是一个由两级分配器构成的内存管理器,当申请的内存大小大于128byte时,就启动第一级分配器通过malloc直接向系统的堆空间分配,如果申请的内存大小小于128byte时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲块列表取表头块给用户。
这种做法有几个优点:
      (1) 小对象的快速分配。小对象是从内存池分配的,这个内存池是系统调用一次malloc分配一块足够大的区域给程序备用,当内存池耗尽时再向系统申请一块新的区域,整个过程类似于批发和零售,起先是由allocator向总经商批发一定量的货物,然后零售给用户,与每次都向总经商要一个货物再零售给用户的过程相比,显然是快捷了。当然,这里的一个问题是,内存池会带来一些内存的浪费,比如当只需分配一个小对象时,为了这个小对象可能要申请一大块的内存池,但这个浪费还是值得的,况且这种情况在实际应用中也并不多见。
     (2) 避免了内存碎片的生成。程序中的小对象的分配极易造成内存碎片,给操作系统的内存管理带来了很大压力,系统中碎片的增多不但会影响内存分配的速度,而且会极大地降低内存的利用率。以内存池组织小对象的内存,从系统的角度看,只是一大块内存池,看不到小对象内存的分配和释放。

     3)尽可能最大化内存的利用率。当内存池尚有的空闲区域不足以分配所需的大小时,分配算法会将其链入到对应的空闲列表中,然后会尝试从空闲列表中寻找是否有合适大小的区域,
但是,这种内存分配器局限于STL容器中使用,并不适合一个通用的内存分配。因为它要求在释放一个内存块时,必须提供这个内存块的大小,以便确定回收到哪个free list中,而STL容器是知道它所需分配的对象大小的,比如上述:
    stl::vector<int> array;
array是知道它需要分配的对象大小为sizeof(int)。一个通用的内存分配器是不需要知道待释放内存的大小的,类似于free(p)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值