STL源码笔记之空间配置器

      整个STL的操作对象都放在容器之内,而容器一定是需要空间配置器以置放资料。空间配置器需要提供如下接口(下面只列出了主要的接口)

template<class T>
class allocator{
public:
     typedef  T value_type;
     typedef  T* pinter;
     typedef  const T*  const_pointer;
     typedef  T&  reference;
     typedef  const  T&  const_reference;
     typedef  size_t  size_type;
     typedef  ptrdiff_t  difference_type;
     pointer allocator::allocate(size_type n,const void* =0);//配置空间
     void allocator deallocate(pointer p,size_type n);//归还配置的空间
     void allocator::construct(pointer p,const T& x);//在配置的空间中初始化对象 等价于new((void*)p)T(x)
     void allocator::destroy(pointer p);//删除对象,等价于p->~T()
}

SGI分别定义了一个标准的空间配置器(std::allocator)和次配置力的空间配置器(std::alloc)


SGI标准的空间配置器

      SGI定义了一个符合部分标准、名为allocator的配置器,但是SGI从未用过它,主要原因是效率太低。只把C++中的::operator new和::operator delete简单的封装。allocator类主要利用了下面两个函数来进行空间的配置和空间的归还。

template<class T>
inline T* allocate(ptrdiff_t size,T*)
{
     set_new_handler(0);
     T *tmp = (T*)(::operator new((size_t)size*sizeof(T)));
     if(tmp == 0){
 	  cerr<<"out of memory"<<endl;
          exit(1);
     }
     return tmp;
}

template<class T>
void deallocate(T* buffer){
    ::operator delete(buffer);
}

SGI特殊的空间配置器

上面的allocator只是基层内存配置/释放行为(::operator new和::operator delete)的一层封装,并没有考虑任何效率上的优化。

          FOO *pf = new FOO;//new包含两阶段:(1)::operator new配置内存;(2)调用FOO构造函数

          delete pf;delete包含两阶段:(1)析构函数;(2)调用::operator delete释放内存。

STL allocator将上面两阶段分开,定义于<memroy>中,主要包含:

         (1)#include<stl_alloc.h>  //负责内存空间的申请与释放,定义了一、二级配置器彼此合作,配置器名为    alloc;

         (2)#include<stl_construct.h>//负责对象的构造与析构,定义了全局函数construct()和destroy()函数;

         (3)#include<stl_uninitialized.h>//定义了一些全局函数用来fill(填充)或者copy(复制)大块内存数据。定义 了un_initialized_copy()、un_initialized_fill()、un_initialized_fill_n()。这些函数不属于配置器的范畴,但是与对象初值设置有关。这些函数最差情况调用construct,最佳情况调用memmove()进行内存数据移动。


构造和析构基本工具

     正如上面所讲,构造和析构的工具在<stl_construct.h>中完成。这里面有两个函数construct()和distroy()函数。construct的版本主要是这样的:

template<class T1, class T2>
inline void construct(T1 *p1,const T2& value)
{ new (p)T1(value); }

destroy有四个版本:

inline void destroy(char*,char*){}
inline void destroy(wchar_t *,wchar_t *){}
template<class T>
inline void destroy(T *p)
{ p->~T(); }
template<class FowardIterator>
inline void destroy(FowardIterator first,FowardIterator last)
{ __destroy(first,last,value_type(first)); }


空间的配置与释放

     对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,STL对此的设计哲学如下:向system heap要求空间;考虑多线程(multi-threads)状态;考虑内存不足时的应变措施;考虑过多“小型区块”可能造成的内存碎片(fragment)问题。

          C++的内存配置操作是:operator new(),内存的释放基本操作是::operator delete()。这两个全局函数相当于C的malloc()和free()函数。SGI正是以malloc和free完成内存的配置和释放。

      考虑到小型区块所可能造成的内存破碎问题。SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略;当配置区块超过128Bytes,视之为足够大,使用第一级配置器。但配置块小于128Byte时,视之为过小,为了降低额外负担,便采用复杂的memory pool整理方式。整个配置器开放哪一级配置器取决于__USE_MALLOC是否被定义:

#ifdef __USE_MALLOC
   typedef  __malloc_alloc_template<0> malloc_alloc;
   typedef malloc_alloc alloc; //第一级配置器
#else
   typedef  __default_alloc_template<__NODE_ALLOCATOR_THREADS,0> alloc;//第二级配置器
#endif
//SGI再包装了一个接口 simple_alloc
template<class T,class Alloc>
class simple_alloc{
public:
    static  T* allocate(size_t  n)
    {return 0==n?0:(T*)Alloc::allocate(n*sizeof(T))}//这里只是一个转调用
    static  T* allocate(void)
    {return  (T*)Alloc::allocate(sizeof(T))}//这里只是一个转调用
    static  T* deallocate(T *p)
    {if(0!=n)Alloc::deallocate(p,n*sizeof(T))}//这里只是一个转调用
    static  T* deallocate(T *p)
    {Alloc::deallocate(p,sizeof(T))}//这里只是一个转调用
}
template<class T,class Alloc=alloc>//缺省调用alloc为配置器
class vector{
protected:
    typedef simple_alloc<value_type,Alloc>data_allocator;
    void deallocate(){
       if(..)data_allocator::deallocate(start,end-start);
    }
}

        STL第一级配置器:alloc直接使用malloc(),deallocate直接使用free();模拟C++的set_new_handler以处理内存不足的状况。第一级空间配置器提供了两个对外的接口:allocate和deallocate。(1)allocate采用malloc来分配空间,当分配空间失败的时候,第一级空间配置器模拟了::operator new的set_new_handler功能。set_new_handler是客户来设置的,其功能就是不断尝试释放资源,然后再尝试分配,看能不能成功。如果客户没有设置相应了相应的set_new_handler函数,则allocate会尝试按照set_new_handler提供的功能做,如果没有提供则抛出异常,表示分配不成功。(2)deallocate只是简单的调用free来释放分配好的空间。

        STL第二级配置器:维护16个自由链表(free lists)负责16种小型块的次配置能力,内存池(memory pool)以malloc配置而得,如果内存不足,转去调用第一级配置器;如果需求大于128Bytes,转去调用第一配置器。第二级空间配置器也同样提供了两个接口:

     (1)allocate:这个函数首先会判断要分配的空间是否大于128字节,如果大于就交个第一级空间配置器完成这个功能;如果小于128字节,allocate首先检查free_list能不能满足分配请求,能满足,直接返回。如果free_list相应链表为空,则调用refill函数;refill函数首先尝试从内存池里面分配一些空间给客户,如果内存池不能完全满足客户的要求,则先分配一部分。如果内存池不能满足客户的请求,调用chunk_alloc从堆内存空间分配一些空间,这些空间分成三部分,一部分给用户,一部分给free_list,,最后部分留给内存池。如果堆内存也没有空间了,就尝试从free_list其他链表里面拿出一些空间。如果其他free_list也没空间了,则交给第一级空间配置器,看不能让new_handler指针所指的函数去释放一些空间,不能则抛出异常。

    (2)deallocate:首先会判断要释放的空间是否大于128字节,大于则交给第一级空间配置器去释放,小于则归还到free_list里面。


第二级空间配置器的内存分配实现

       如果区块够大,超过128bytes,就移交第一级空间配置器处理,当小于128bytes时,则以内存池管理。每次配置一大块内存,并维护对应之链表(free-list),下次若有相同大小的内存需求,就立即从free-list中拨出。如果客端归还小额区块,负责回收,将它加到相应的free-list链表中。SGI第二级配置将任何小额区块内存需求量上调至8的倍数(例如30,调至32),并维护16个free-list,各管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128的小额区块:

union obj{
     union obj* free_list_link;
     char client_data[1];
}
         具体操作过程是这样的:先检查free-list,如果有可用的块,则直接拿来用,如果没有可用块,就将区块大小上调至8的倍数,然后调用refill(),准备为free-list重新填充空间。chunk_alloc(32,20),于是malloc配置20*2*32Byte区块,第一个区块返回个用户,19个32Bytes块交个free-list维护,20个32Bytes块留给内存池。接下来chunk_alloc(64,20),此时free-list[7]为空,向内存池要求支持。内存池供应32*20/64=10个块,一个交给用户,另外9个块留给free-list维护。

      万一山穷水尽,整个system heap空间都不够了,chunk_alloc()四处寻找有无”尚未用区块“之free-list,找出一块就挖一块交出,找不到就交给第一个空间配置器。第一级配置器也是使用malloc来配置内存,但是有out-of-memory处理机制。

内存基本处理工具

    STL定义了五个全局函数,作用于未初始化空间上。它们是construct()、destroy()、uninilialized_copy()、uninilialized_fill()、uninilialized_fill_n()。用这些函数包含<memory>,不过实际定于<stl_uninitialized>


你可能感兴趣的:(STL源码笔记之空间配置器)