1. 程式人生 > >STL 二級空間配置器實現機制

STL 二級空間配置器實現機制

一:簡述

STL 空間配置器(allocator)中分配記憶體與構造物件分開,在標頭檔案<memory>定義:

1、分配記憶體由alloc::allocate()負責,記憶體釋放由alloc::deallocate()負責,在<stl_alloc.h>定義。

2、構造物件由::construct()負責,物件析構由::destroy()負責,在<stl_construct.h>定義

//以下是第二級配置器__default_alloc_template定義
//notice:沒有"template"型別引數,第二個引數沒有派上用處
//第一個引數用於多執行緒環境下,目前不討論
template <bool threads, int inst>
class __default_alloc_template
{
private:
    //ROUND_UP()將bytes提升至8的倍數
    static size_t ROUND_UP(size_t bytes)
    {
        return ((bytes + __ALIGN - 1) & ~(__ALIGN - 1));
    }
private:
    union obj       //free_lists節點構造
    {
        union obj * free_list_link;
        char client_data[1];
    };
private:
    //16個free_lists
    static obj * volatile free_list[__NFREELISTS];
    //根據區塊大小,決定使用第n號free-list, n從1算
    static size_t FREELIST_INDEX(size_t bytes)
    {
        return (((bytes) + __ALIGN - 1) / __ALIGN - 1);
    }
    //返回一個大小為n的物件,並可能加入大小為n的其他區塊到free list
    static void *refill(size_t n);
    //配置一大塊空間,可容納nobjs個大小為"size"的區塊
    //如果配置nobjs個區塊有所不便,nobjs可能會降低
    static char* chunk_alloc(size_t size, int& nobjs);

    // chunk allocation state
    static char *start_free;    //記憶體池起始位置
    static char *end_free;      //記憶體池結束位置
    static size_t heap_size;    

public:
    static void * allocate(size_t n);
    static void deallocate(void *p, size_t n);
    static void * reallocate(void *p, size_t old_sz, size_t new_sz);
};
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;

template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;

template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

template <bool threads, int inst>
typename __default_alloc_template<threads, inst>::obj * volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

二、空間配置器在容器中的使用

   例如:STL中vector部分實現:


templete <class T,class Alloc = alloc>   //alloc定義如下

class vector{

    protected:
        // simple_alloc 定義如下
        typedef simple_alloc<value_type,Alloc> data_allocator; 

        void deallocate(){
            if(...)
                  data_allocator::deallocate( start,end_of_storage - start );
          }
    ...
}


//alloc定義:
//考慮到小型區塊可能造成的記憶體破碎問題,SGI設計了雙層級配置器
//SGI STL通過巨集定義來決定是否使用一級空間配置器或二級空間配置器
#ifdef _USE_MALLOC  
    ...
    //令alloc為一級配置器
    typedef _malloc_alloc_template<0> malloc_alloc;
    typedef malloc_alloc alloc;

#else
    ...
    //令alloc為二級配置器
    typedef _default_alloc_template<_NODE_ALLOCATOR_THREADS,0> alloc;

#endif


//simple_alloc介面定義
//無論alloc被如何定義,SGI 仍定義一個包裝介面,使配置器的介面能夠符合STL規範:
//SGI STL容器全都使用這個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 void deallocate(T *p,size_t n){
            if(0 !=n ){
                Alloc::delallocate( p, n*sizeof(T) );
            }
        }

        static void deallocate(T *p){
            Alloc::deallocate(  p,sizeof(T) );
        }
}

三、二級配置器 _default_alloc_template::allocate( ) 實現機制:

1、小的記憶體需求,就直接從free-lists直接拔出。如果需要釋放小額區塊,就有配置器回收到free-list中。free-lists管理16中小額區塊,各自管理大小分別為8,16,24,32,40,48,56,64,72,80,88,96,1,04,112,120,128

2、為了方便管理,SGI第二配置器會主動將任何小額區塊上調至8的倍數,比如需要分配30bytes,實際分配32bytes。

3、程式碼:

template<bool threads, int inst>
void * __default_alloc_template<threads, inst>::allocate(size_t n)
{
    obj * volatile *my_free_list;
    obj * result;
    //大於128位元組就呼叫第一級配置器
    if(n > (size_t)__MAX_BYTES)
    {
        return malloc_alloc::allocate(n);
    }
    //尋找16個free lists中適當的一個
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if(result == 0)
    {
        //沒找到free_list,準備重新填充free list
        void *r = refill(ROUND_UP(n));
        return r;
    }
    //調整free list
    *my_free_list = result->free_list_link;
    return (result);
}


//返回一個大小為n的物件,並且可能會為適當的free_list增加節點
//假設size已經適當上調至8的倍數
template <bool threads , int inst>
void *__default_alloc_template<threads , inst>::refill(size_t n)
{
	int nobjs = 20;
	char *chunk = chunk_alloc(n , nobjs)
	Obj * volatile *my_free_list;
	Obj * result;
	Obj * current_obj , *next_obj;
	int i;
 
	if( nobjs == 1)
		return chunk;
	my_free_list = free_list + FREELIST_INDEX(n);
	result = (Obj *)chunk;
	*my_free_list = next_obj = (Obj *)(chunk + n);
	for (i = 1 ; ; ++i ) {
		current_obj = next_obj;
		if (nobjs - 1 == i){
			current_obj -> free_list_link = 0;
			break;
		}
		else {
			current_obj->free_list_link = next_obj;
		}
	}
}


//假設size已經適當上調至8的倍數
//注意引數nobjs是pass bu reference
template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs;
    size_t bytes_left = end_free - start_free;  //記憶體池剩餘空間

    if(bytes_left >= total_bytes)
    {
        //記憶體池剩餘空間完全滿足需求量
        result = start_free;
        start_free += total_bytes;
        return (result);
    }
    else if(bytes_left >= size)
    {
        //記憶體池剩餘空間不能完全滿足需求量,但足夠供應一個含以上區塊
        nobjs = bytes_left / size;
        total_bytes = size * nobjs;
        result = start_free;
        start_free += total_bytes;
        return (result);
    }
    else
    {
        //記憶體池剩餘空間連一個區塊額大小都無法提供
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
        //以下試著讓記憶體池的殘餘零頭還有利用價值
        if(bytes_left > 0)
        {
            //記憶體池還有一些零頭,先配置給適當的free_list
            //首先尋找適當的free_list
            obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
            //調整free_list,將記憶體池中的殘餘空間編入
            ((obj*)start_free)->free_list_link = *my_free_list;
            *my_free_list = (obj*)start_free;
        }

        //配置heap空間,用來補充記憶體池
        start_free = (char*)malloc(bytes_to_get);
        if(0 == start_free)
        {
            //heap空間不足,malloc()失敗
            int i;
            obj *volatile * my_free_list, *p;
            //搜尋適當的fee_list
            //適當即"尚有未用區塊。且區塊足夠大"
            for(i = size; i <= __MAX_BYTES; i += __ALIGN)
            {
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                if(0 != p)
                {
                    //free_list 內尚有未用區塊
                    //調整free_list以釋放未用區塊
                    *my_free_list = p->free_list_link;
                    start_free = (char*)p;
                    end_free = start_free + i;

                    return (chunk_alloc(size, nobjs));
                }
            }
            end_free = 0;
            start_free = (char*)malloc_alloc::allocate(bytes_to_get);
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;

        return (chunk_alloc(size, nobjs));
    }
}

4、 總結為七種情況:

     1、分配空間大於 128 bytes,呼叫級空間配置器(對malloc( )的簡單封裝)(清況1)

           若分配空間小於  128 bytes :根據的自由連結串列下標分配空間:

     2、自由連結串列對應 free_list 指向不為空,說明有未用空間,調整free_list,分配空間  (清況2)

           若自由連結串列對應free_list 指向為空,呼叫 refill( ) 函式,從記憶體池分配空間:

     3、記憶體池空間大於要分配空間   ( bytes_left  >= total_bytes ),調整記憶體池位置(start,end )  (清況3)

     4、記憶體池空間只能放一個數據(bytes_left  >=  size),分配並調整記憶體池位置  (清況4)

          若一個數據都放不下(bytes_left < size),將記憶體池中的殘餘空間先編入(頭插到 free_list),並用malloc ( )來分配空間:

      5、malloc( )分配成功,調整記憶體池位置,遞迴呼叫refill( )進行分配  (清況5)

           若malloc( )分配失敗,在free_list 尋找有無“未用區塊”:

      6、free_list 有可用區塊,調整free_list 以釋放未用區塊,調整記憶體池位置,遞迴呼叫refill( )進行分配  (清況6)

      7、free_list 無可用區塊,此時山窮水盡,呼叫一級空間配置器,看看 out_of_memory 機制能否盡力  (清況7)

 四、空間釋放函式_default_alloc_template::deallocate( )函式

static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj *volatile * my_free_list;
    //大於128就呼叫第一級配置器
    if( n > (size_t)_MAX_BYTES ){
        malloc_allocL::deallocate(p,n);
        return ;
    }
    //尋找對應的free_list
    my_free_list = free_list + FREESLIST_INDEX(n);
    //調整free_list,回收區塊
    q->free_list_link = *my_free_list;
    *my_free_list= q;
    
}

五、構造物件函式::construct( )

#include <new.h>

template <class T1, class T2>
inline void construct(T1 *p ,const T2 &value)
{
    new (p) T1(value);    //placement new; 呼叫T1::T1(value);
}

六、析構物件函式::destroy( )

//第一版本,接受一個指標,直接呼叫解構函式全部析構
template <class T>
inline void destroy(T* pointer) 
{
    pointer->~T();
}

//以下是destroy()第二版本,接受兩個迭代器
//該函式設法找出元素的數值型別,進而利用__type_traits<>求取最適當措施
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
  _destroy( first, last, value_type(first) );
}

//判斷元素的數值型別(value_type)是否有trivial destructor
template <class ForwardIterator, class T>
inline void _destroy(ForwardIterator first,ForwardIterator last, T*)
{
  typedef typename _type_traits<T>::has_trivial_destructor trivial_destructor;
  _destroy_aux(first, last, trivial_destructor() );
}

//如果元素型別為non-trivial destructor(有意義),逐一刪除元素
template <class ForwardIterator>
inline void _destroy_aux(ForwardIterator first, ForwardIterator last, false_type)
{
  for ( ; first != last; ++first)
       destroy(&*first);
}

//如果元素型別為trivial destructor(無傷大雅),什麼也不做
template <class ForwardIterator>
inline void _destroy_aux(ForwardIterator, ForwardIterator, true_type) {}

//以下是destroy()第二版本針對迭代器為char *和wchar_t的特化版
inline void destroy(char *,char *){}
inline void destroy(wchar_t *,wchar_t *) {}