STL-ch2-空間配置器

STL空間配置器

空間配置器——《STL源碼剖析》chapter2

2.1 STL標準規定的空間配置器的標準接口

  • STL規定的空間配置器的名稱是allocator
  • 包含下列typedef:
    • value_type
    • pointer/const_pointer
    • reference/const_reference
    • size_type
    • difference_type
  • 包含allocator::rebind
  • 包含成員函數:allocate(), deallocate(), construct(), destroy()。這4個全是成員函數,作用分別是:配置空間,釋放配置空間,構造對象,析構對象。
  • SGI實現的STL未遵守STL標準的規定,使用的是一個具有次配置能力(sub-allocation)的特殊配置器。

2.2 具有次配置能力的SGI STL空間配置器

2.2.1 SGI也實現了STL標準規定的std::allocator

但是不建議用(只是對operator newoperator delete做了簡單封裝)

2.2.2 SGI中的特殊配置器名字叫std::alloc

  • class Foo{...};
    Foo *pf = new Foo;
    delete pf;
    

    上面的new算式包含兩階段操作:1. 調用operator new配置內存,2. 調用Foo::Foo()構造函數對象。

    delete算式也包含兩階段操作:1. 調用Foo::~Foo()將對象析構,2. 調用operator delete釋放內存。

  • 爲了精密分工,STL配置器將這兩階段操作區分開來。內存的配置和釋放由alloc成員函數alloc::allocate()alloc::deallocate()來負責,對象的構造和析構由全局函數::construct()::destroy()來負責。

  • 標準規定,配置器定義於頭文件<memory>中,<memory>包含兩個文件:#include<stl_alloc.h>(這裏面定義了一二級配置器)和#include<stl_construct.h>(這裏定義了全局的::construct()::destroy())。

2.2.3 構造和析構基本工具::construct()::destroy()

  • ::construct():利用placement new在傳入的地址上構造對象。
  • ::destroy():有多個版本。1. 接受一個指針類型:直接調用指針指向對象的析構函數;2. 接受兩個迭代器,需要先判斷類型的析構函數是否trival(無關緊要),如果trival就什麼都不做,如果不trival就遍歷,依次調用上一個版本的destroy()

2.2.4 空間的配置與釋放std::alloc

  • 考慮小區塊造成的內存破碎問題,SGI設計了雙層級配置器。
  • 第一級:直接使用malloc()free()
  • 第二級:
    • 如果配置區塊大小超過128byte,直接用malloc()free()
    • 如果配置區塊大小不足128byte,爲降低額外負擔,採用複雜的memory pool整理方式。

2.2.5 第一級配置器malloc_alloc

  • template<int inst>
    class __malloc_alloc_template__{...};
    //別名
    typedef __malloc_alloc_template__<0> malloc_alloc;
    
  • allocate()直接使用malloc()

  • deallocate()直接使用free()

  • 模擬C++的set_new_handler()以處理內存不足的狀況。

2.2.6 第二級配置器

  • sub-allocation:每次配置一大塊內存,並維護對應之自由鏈表(free-list)。下次若再有相同大小的內存需求,就直接從free-lists中拔出。如果客端釋還小額區塊,就由配置器回收到free-list中。

  • 第二級配置器會自動將內存需求量上調至8的倍數,並維護16個free-lists(分別管理8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小額區塊)

  • free-list節點的結構

    union obj{
        union obj * free_list_link;
        char client_data[1];
    };
    

    使用union,一物二用,每個節點不需要額外的指針來維護鏈表。

2.2.7 空間配置函數allocate()

  • 源碼:

    // n must be > 0
    static void * 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;第二步:將result指向這個free-list的開頭;第三步:調整free-list。二三兩步就是將一個區塊從free-list中摘下來。

  • 如果沒有可用的free-list,調用refill()重新填充free-list

2.2.8 空間釋放函數deallocate()

  • 源碼:

    //p 不可以是0
    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_alloc::deallocate(p,n);
            return;
        }
        
        my_free_list = free_list + FREELIST_INDEX(n);
        //調整free-list,回收區塊
        q->free_list_link = *my_free_list;
        *my_free_list = q;
    }
    

2.2.9 重新填充refill()

  • 當free-list沒有可用區塊的時候,refill()從內存池中獲取新的空間。

  • 缺省獲得20個區塊大小的空間,若內存池空間不足,實際獲取的區塊可能不足20。

  • 源碼

    //假設n已經上調至8的倍數
    template <bool threads, int inst>
    void* __default_alloc__template<threads,inst>::refill(size_t n)
    {
        int nobjs = 20;
        //調用chunk_alloc(),嘗試獲得nobjs個區塊作爲free-list的新節點
        //注意參數nobjs是pass by reference
        char* chunk = chunk_alloc(n,nobjs);
        obj * volatile * my_free_list;
        obj * result;
        obj * current_obj, * next_obj;
        int i;
        
        //如果只有一個區塊,這個區塊就分配給調用者用,free-list無新節點
        if(n==1) return (chunk);
        //否則準備調整free-list
        my_free_list = free_list + FREELIST_INDEX(n);
        
        //以下在chunk空間內建立free-list
        result = (obj*) chunk;	//這一塊準備返回給客端
        //以下引導free-list指向新配置的空間
        *my_free_list = next_obj = (obj*)(chunk+n);
        //以下將free-list的各節點串聯起來
        for(i=1;;i++){
            current_obj = next_obj;
            next_obj = (obj*)((char*)next_obj+n);
            if(nobjs-1 == i){
                current_obj->free_list_link = 0;
                break;
            }else{
                current_obj->free_list_link = next_obj;
            }
        }
        return (result);
    }
    

2.2.10 內存池

  • chunk_alloc()從內存池中取空間給free-list使用,在refill()中被調用。

  • template <bool threads, int inst>
    char*
    __default_alloc_template__<threads,inst>::
    chunk_alloc(size_t size, int& nobjs)
    {
        //···
    }
    
  • chunk_alloc()的格式如上面所示,根據傳進來的sizenobjs分配空間。

  • 首先在__default_alloc_template__類中有兩個指針:start_freeend_free,兩個指針都是static char*格式,分別指向內存池起始位置和內存池結束位置。

  • chunk_alloc()end_free-start_free判斷內存池的水量,如果水量充足,就直接調出20個區塊返回給free-list。

  • 如果水量不足以提供20個區塊,但還足夠提供一個以上的區塊,就撥出這不足20個區塊的空間出去。此時pass by reference的nobjs參數將被修改爲實際能提供的區塊數。

  • 如果內存池連一個區塊空間都不夠了,就調用malloc從heap中配置內存,爲內存池注入源頭活水以應付需求。

  • 萬一山窮水盡,整個system heap空間都不夠了,malloc失敗,chunk_alloc就四處尋找有無“尚有未用區塊,且區塊足夠大”之free-list。找到了就挖一塊交出,找不到就調用第一級配置器(寄希望於第一級配置器的out-of-memory處理機制)。如果可以就成功,否則就拋出bad_alloc異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章