1. 程式人生 > >stl--分析空間配置器及原始碼實現

stl--分析空間配置器及原始碼實現

最近真是懶癌犯了,好久沒動過部落格了。開學兩個月了,每天基本都“住”在網咖了,沒日沒夜和舍友打遊戲。可是畢竟大三狗,找實習的壓力真是大。是時候搞出點事情了。

前兩天把stl的空間配置器的思路搞懂了,今天就把一二級配置器自己實現了一番,簡直爽,原始碼果然博大精深,只可惜我道行不夠,實現了好久,才把個中原理搞清楚一些。

現在就開始聊聊空間配置器吧

首先來說說一級空間配置器:

一級是最簡單的了,其實就是封裝了malloc和free以及realloc三大函式,不過作者有什麼比較心思細微的地方呢?

來看看原始碼剖析一下

typedef void (*FUNC)();
template <int inst>
class __malloc_alloc_template
{
private:
    //未分配成功處理函式
    static void *oom_malloc(size_t);
    static void *oom_realloc(void *,size_t);

    //個人定製函式,用於釋放空間
    static FUNC __malloc_alloc_oom_handler;
public:
    static void *allocate(size_t n)
    {
        void *result = NULL;

        result = malloc(n);
        if(result == NULL){
            result = oom_malloc(n);
        }

        return result;
    }
    static void *reallocate(void *p,size_t n)
    {
        void *result = NULL;

        result = realloc(p,n);
        if(result == NULL){
            result = oom_realloc(p,n);
        }

        return result;
    }
    static void *deallocate(void *p, size_t n)
    {
        free(p);
    }
    //返回之前的__malloc_alloc_oom_handler函式,把新函式給__malloc
    static FUNC set_malloc_handler(FUNC f)
    {
        FUNC old;

        old = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;

        return old;
    }
};

我只拷貝了一部分,加黑的如果瞭解了那麼一級配置器就搞定了,首先說說看這個oom_malloc,當malloc失敗時,這個函式就被呼叫。它的作用就是去呼叫使用者自定義的釋放空間的一個函式__malloc_alloc_oom_handler,不斷的去釋放空間,直到可以申請空間為止,如果實在不成功就會丟擲異常,結束程式

oom_realloc和oom_malloc大同小異,幾乎是一樣的。惟一的區別在於前者是realloc申請空間,後者是malloc申請空間

接下來有意思的來了,可能很多看過stl原始碼剖析的同學(其實是我開始不懂啦- -)會對void (*set_malloc_handler(void (*f)()))()
產生疑問,媽的這時什麼!!

好,現在我來解答這個疑問,看到typedef void (*FUNC)() 了麼,FUNC是一種型別,它定義的變數是指向函式的指標,如果我把上面的函式寫成這樣是不是就比較好看懂了呢static FUNC set_malloc_handler(FUNC f)  說白了,就是一個函式它的引數是個指標,它的返回值也是個指標,就這樣!!!

如果細一點說,那麼引數和反回值都是指向函式的指標。懂了麼~~

現在聊聊第二步,二級配置器
二級配置器就是在申請128位元組以下會用到的東東。
來一張圖片,對著圖片詳細說說咱們的二級空間配置器

針對這張圖,首先來解釋一下橫行那些是代表這一個空閒的陣列(總共有16個元素,我沒有畫全),每一個元素都是一個指標。
其中這個空閒陣列的每一個元素指向了它們自己對應的空閒連結串列

每一條空閒連結串列的每一塊是根據對應 (陣列下標+1)*8 作為一塊的大小。
而start_free和end_free是多劃分出的空閒區,並不屬於哪個連結串列中。這一點稍後去說。
其實這個資料結構讓我看來有種雜湊表的味道,嘖嘖。真是有點美味啊!!
其實這個陣列是沒有什麼好說的,比較讓人覺得製作新奇且高效的是在掛連結串列的節點操作上。
下面來說說
union obj
{
    union obj *free_list_link;
    char client_data[1];

};

可能有些同學不知道為什麼用這個聯合體,我來詳細解釋一下

這個聯合體是在節點內部的,這樣做的目的是節省了額外空間的開闢。那麼肯定有同學就不瞭解既然在節點內部,拿節點的值不是把它覆蓋了麼,那怎麼可能去儲存下一個節點的位置

確實聯合體的內容會被覆蓋,但是被覆蓋前它就已經去讓空閒連結串列的指標指向下一個節點了,所以這是它精妙之處

這個就是用作存放指向下一個節點的地址的聯合體。怎麼操作這個聯合體呢?如何去把節點掛上去呢?
恩~~兩步就解決了
為了解決方便我自定義一個連結串列的首節點好了 union *list 連結串列的節點大小設定為n
接下來需要有兩個輔助的指標,一個是obj *cur一個是 obj *next;

//先讓next和cur儲存為連結串列頭節點地址
next = cur = list;
//因為連結串列地址空間連續,所以讓next+n
next = (obj *)((char *)next + n);
//然後讓cur的下一個節點指向next
cur->free_list_link = next;

如何回收呢?
回收的方法是這樣的
根據傳入的指標p和用的空間n
obj *l;
l = (obj *)p;
l->next = list;
list = l;
給一個圖應該比較好懂一些

list指向的空閒連結串列開始的地方,p指向的是要釋放的空間,目的就是讓list指向p指向的位置就是這樣,還有多的一步就是要把節點掛到鏈上。

現在可以講講空閒區間的問題了

空閒區間有3種去留:
(1).就是空閒區間
(2).被分配出去
(3).如果空閒區間不夠要分配的空間,那麼就會被掛到對應的連結串列上

大概就是這樣,大體上就是這些,我下面把程式碼貼上來,可以執行的。

//二級空間配置器
enum{__ALIAN = 8}; //邊界
enum{__MAX_BYTES = 128}; //最大分配塊
enum{__NFREELISTS = __MAX_BYTES / __ALIAN};//空閒連結串列個數

template <bool threads, int inst>
class __default_alloc_template
{
public:
    static void *allocate(size_t n)
    {
        //一級的指標不能被編譯器優化,因為申請空間時會迭代去掛鏈
        //很有可能編譯器把它優化到暫存器中
        obj *volatile *my_free_list;
        obj *result;

        if(n > (size_t)__MAX_BYTES){
            return (malloc_alloc::allocate(n));
        }
        my_free_list = free_list + FREELIST_INDEX(n);
        result = *my_free_list;
        if(0 == result){
            //沒有找到freelist
            void *r = refill(ROUND_UP(n));
            return r;
        }
        *my_free_list = result->free_list_link;
        return result;
    }
    static void deallocate(void *p,size_t n)
    {
        obj *q = (obj *)p;
        obj *volatile *my_free_list;

        if(n > (size_t) __MAX_BYTES){
            malloc_alloc::deallocate(p,n);
            return;
        }
        my_free_list = free_list + FREELIST_INDEX(n);
        //讓my_free_list往前指一個單位,q->next = p; p = q;
        q->free_list_link = *my_free_list;
        *my_free_list = q;
    }
    static void *reallocate(size_t n);
private:
    static size_t ROUND_UP(size_t n)
    {
        //如果二進位制的後三位不為0,那麼就去進位,然後和7的反相與,去除後三位
        //是8的倍數
        return (n+__ALIAN-1) & ~(__ALIAN-1);
    }
private:
    union obj
    {
        union obj *free_list_link; //指向下一個節點
        char first_block[1];       //下一個節點的首地址的內容
    };
private:
    static obj *volatile free_list[__NFREELISTS];
    static size_t FREELIST_INDEX(size_t n)
    {
        return ROUND_UP(n) / __ALIAN - 1;
    }
    //free_list沒有可用塊時呼叫refill
    static void *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(1 == nobjs)
            return chunk;
        my_free_list = free_list + FREELIST_INDEX(n);

        result = (obj *)chunk;
        //引導free list指向新配置的空間
        *my_free_list = next_obj = (obj *)(chunk + n);
        //不斷的去掛鏈
        for(i = 0; ; ++i){
            current_obj = next_obj;
            next_obj = (obj *)(next_obj + n);
            //最後一塊就讓它置空
            if(nobjs - 1 == i){
                current_obj->free_list_link = 0;
                break;
            }
            current_obj->free_list_link = next_obj;
        }

        return result;
    }
    //配置一大塊
    static char *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 = nobjs * size;
            result = start_free;
            start_free += size;
            return result;
        }else{
            //記憶體池連一塊大小都不能提供
            size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
            //把多出的剩餘空間掛到空閒連結串列上
            if(bytes_left > 0){
                obj *volatile *my_free_list = free_list + FREELIST_INDEX(bytes_left);
                *my_free_list = (obj *)start_free;
            }
            start_free = (char *)malloc(bytes_to_get);
            if(0 == start_free){
                int i;
                obj *volatile *my_free_list, *p;
                //去空閒連結串列找
                for(i = size; i < __MAX_BYTES; i += __ALIAN){
                    my_free_list = free_list + FREELIST_INDEX(i);
                    p = *my_free_list;
                    if(0 != p){
                        *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);
        }
    }

    static char *start_free;
    static char *end_free;
    static size_t heap_size;
};
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>
//obj *volatile是型別,並且該型別屬於類中
//typename __default_alloc_template<threads, inst> :: obj* volatile
union a
{
};
a *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};
typedef __default_alloc_template<0,0> alloc;