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 *) {}