上一篇我們介紹了STL對象的構(gòu)造與析構(gòu),這篇介紹STL內(nèi)存的配置與釋放。

STL有兩級空間配置器,默認是使用第二級。第二級空間配置器會在某些情況下去調(diào)用第一級空間配置器。空間配置器都是在allocate函數(shù)內(nèi)分配內(nèi)存,在deallocate函數(shù)內(nèi)釋放內(nèi)存。

 

第一級空間配置器

 

第一級配置器只是對malloc函數(shù)和free函數(shù)的簡單封裝,在allocate內(nèi)調(diào)用malloc,在deallocate內(nèi)調(diào)用free。同時第一級配置器的oom_malloc函數(shù),用來處理malloc失敗的情況。如下所示:

allocate對malloc函數(shù)簡單封裝 :

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

static void *allocate(size_t n)
{          void *result = malloc(n);           if (NULL == result)
                    result = oom_malloc(n);           return result;
}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

deallocate對free函數(shù)簡單封裝 :

static void deallocate(void *p, size_t) { free(p); }

 

oom_malloc調(diào)用外部提供的malloc失敗處理函數(shù),然后重新試著再次調(diào)用malloc。重復執(zhí)行此過程,直到malloc成功為止 : 

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

template <int inst>void* __malloc_alloc<inst>::oom_malloc(size_t n)
{        void (*my_malloc_handler)();        void *result;        for (;;)
        {
                my_malloc_handler = malloc_alloc_oom_handler;                if (NULL == my_malloc_handler)
                        __THROW_BAD_ALLOC;
                (*my_malloc_handler)();
                result = malloc(n);                if (result)                        return result;
        }
}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

 

第二級空間配置器

第一級配置器直接調(diào)用malloc和free帶來了幾個問題:

1.內(nèi)存分配/釋放的效率低。2. 當配置大量的小內(nèi)存塊時,會導致內(nèi)存碎片比較嚴重。3. 配置內(nèi)存時,需要額外的部分空間存儲內(nèi)存塊信息,所以配置大量的小內(nèi)存塊時,還會導致額外內(nèi)存負擔。

 

第二級配置器維護了一個自由鏈表數(shù)組,每次需要分配內(nèi)存時,直接從相應的鏈表上取出一個內(nèi)存節(jié)點就完成工作,效率很高。

 

自由鏈表數(shù)組

自由鏈表數(shù)組其實就是個指針數(shù)組,數(shù)組中的每個指針元素指向一個鏈表的起始節(jié)點。數(shù)組大小為16,即維護了16個鏈表,鏈表的每個節(jié)點就是實際的內(nèi)存塊,相同鏈表上的內(nèi)存塊大小都相同,不同鏈表的內(nèi)存塊大小不同,從8一直到128。如下所示,obj為鏈表上的節(jié)點,free_list就是鏈表數(shù)組。

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

//自由鏈表union obj
{
          union obj *free_list_link;          char data[1];
};//自由鏈表數(shù)組static obj *volatile free_list[__NFREELISTS];

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

內(nèi)存分配

allocate函數(shù)內(nèi)先判斷要分配的內(nèi)存大小,若大于128字節(jié),直接調(diào)用第一級配置器,否則根據(jù)要分配的內(nèi)存大小從16個鏈表中選出一個鏈表,取出該鏈表的第一個節(jié)點。若相應的鏈表為空,則調(diào)用refill函數(shù)填充該鏈表。如下:

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

template <bool threads>void *__default_alloc<threads>::allocate(size_t n)
{
        obj *volatile *my_free_list;
        obj *result;        if (n > (size_t)__MAX_BYTES) //調(diào)用第一級配置器
                return malloc_alloc::allocate(n);
        my_free_list = free_list + FREELIST_INDEX(n);
        result = *my_free_list;        if (result == NULL)
        {                //第n號鏈表無內(nèi)存塊,則準備重新填充該鏈表
                void *r = refill(ROUND_UP(n));                return r;
        }        *my_free_list = result->free_list_link;        return result;
}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

填充鏈表

若allocate函數(shù)內(nèi)要取出節(jié)點的鏈表為空,則會調(diào)用refill函數(shù)填充該鏈表。

refill函數(shù)內(nèi)會先調(diào)用chunk_alloc函數(shù)從內(nèi)存池分配一大塊內(nèi)存,該內(nèi)存大小默認為20個鏈表節(jié)點大小,當內(nèi)存池的內(nèi)存也不足時,返回的內(nèi)存塊節(jié)點數(shù)目會不足20個。接著refill的工作就是將這一大塊內(nèi)存分成20份相同大小的內(nèi)存塊,并將各內(nèi)存塊連接起來形成一個鏈表。如下:

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

template <bool threads>void *__default_alloc<threads>::refill(size_t n)
{        int nobjs = __NOBJS;        char *chunk = chunk_alloc(n, nobjs);  //從內(nèi)存池獲取內(nèi)存
        if (nobjs == 1)  //只能分配一塊,則直接返回給調(diào)用者
                return chunk;
        obj *volatile *my_free_list;
        obj *result, *next_obj, *current_obj;
        result = (obj *)chunk;
        my_free_list = free_list + FREELIST_INDEX(n);        *my_free_list = next_obj = (obj *)(chunk + n);        for (int i = 1; i < nobjs - 1; i++)  //將剩下的區(qū)塊添加進鏈表        {
                current_obj = next_obj;
                next_obj = (obj *)(char *)(next_obj + n);
                current_obj->free_list_link = next_obj;
        }        //最后一塊
        current_obj = next_obj;
        current_obj->free_list_link = NULL;        return result;
}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

內(nèi)存池

chunk_alloc函數(shù)內(nèi)管理了一塊內(nèi)存池,當refill函數(shù)要填充鏈表時,就會調(diào)用chunk_alloc函數(shù),從內(nèi)存池取出相應的內(nèi)存。

在chunk_alloc函數(shù)內(nèi)首先判斷內(nèi)存池大小是否足夠填充一個有20個節(jié)點的鏈表,若內(nèi)存池足夠大,則直接返回20個內(nèi)存節(jié)點大小的內(nèi)存塊給refill。如下:

        if (size_left >= total_size)  //內(nèi)存池剩余空間滿足需求        {
                result = start_free;
                start_free += total_size;                return result;
        }

 

若內(nèi)存池大小無法滿足20個內(nèi)存節(jié)點的大小,但至少滿足1個內(nèi)存節(jié)點,則直接返回相應的內(nèi)存節(jié)點大小的內(nèi)存塊給refill;

        else if (size_left >= size)  //剩余空間不能全部滿足,但至少滿足一塊        {
                nobjs = size_left / size;
                result = start_free;
                start_free += nobjs * size;                return result;

 

若內(nèi)存池連1個內(nèi)存節(jié)點大小的內(nèi)存塊都無法提供,則chunk_alloc函數(shù)會將內(nèi)存池中那一點點的內(nèi)存大小分配給其他合適的鏈表,然后去調(diào)用malloc函數(shù)分配的內(nèi)存大小為所需的兩倍。若malloc成功,則返回相應的內(nèi)存大小給refill;若malloc失敗,會先搜尋其他鏈表的可用的內(nèi)存塊,添加到內(nèi)存池,然后遞歸調(diào)用chunk_alloc函數(shù)來分配內(nèi)存,若其他鏈表也無內(nèi)存塊可用,則只能調(diào)用第一級空間配置器,因為第一級空間配置器有malloc失敗的出錯處理函數(shù),最終的希望只能寄托在那里了。

如下是整個chunk_alloc函數(shù):

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

template <bool threads>char *__default_alloc<threads>::chunk_alloc(size_t size, int& nobjs)
{
        size_t total_size = size * nobjs;        char *result;
        size_t size_left = end_free - start_free;        if (size_left >= total_size)  //內(nèi)存池剩余空間滿足需求        {
                result = start_free;
                start_free += total_size;                return result;
        }        else if (size_left >= size)  //剩余空間不能全部滿足,但至少滿足一塊        {
                nobjs = size_left / size;
                result = start_free;
                start_free += nobjs * size;                return result;
        }        else  //連一個區(qū)塊都無法滿足        {                if (size_left > 0)  //將殘余內(nèi)存分配給其他合適的鏈表                {
                        obj *volatile *my_free_list = free_list + FREELIST_INDEX(size_left);
                        ((obj *)start_free)->free_list_link = *my_free_list;  //在頭部插入
                        *my_free_list = (obj *)start_free;
                }
                size_t bytes_to_get = 2 * total_size + ROUND_UP(heap_size >> 4);
                start_free = (char *)malloc(bytes_to_get);                if (start_free == NULL)  //堆空間不足                {                        int i;
                        obj *volatile *my_free_list;
                        obj *p;                        for (i = size; i < __MAX_BYTES; i++)
                        {
                                my_free_list = free_list + FREELIST_INDEX(i);
                                p = *my_free_list;                                if (p != NULL)
                                {                                        *my_free_list = p->free_list_link;
                                        start_free = (char *)p;
                                        end_free = start_free + i;                                        return chunk_alloc(size, nobjs);
                                }
                        }
                        end_free = NULL;                        //調(diào)用第一級配置器
                        start_free = (char *)malloc_alloc::allocate(bytes_to_get);
                }
                heap_size += bytes_to_get;
                end_free = start_free + heap_size;                return chunk_alloc(size, nobjs);
        }
}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

內(nèi)存釋放

第二級配置器的deallocate函數(shù)并不會直接釋放內(nèi)存塊。當內(nèi)存塊大小大于128字節(jié)時才會直接釋放,否則會將內(nèi)存塊回收到相應的鏈表當中。如下:

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

void __default_alloc<threads>::deallocate(void *p, size_t n)
{        //大于__MAX_BYTES,則釋放該內(nèi)存
        if (n > (size_t)__MAX_BYTES)
                malloc_alloc::deallocate(p, n);
        obj *q = (obj *)p;
        obj *volatile *my_free_list;
        my_free_list = free_list + FREELIST_INDEX(n);        //小于__MAX_BYTES,則回收區(qū)塊,并未釋放
        q->free_list_link = *my_free_list;        *my_free_list = q;
}

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

內(nèi)存對外接口

STL對外提供了一個simple_alloc類,該類提供統(tǒng)一的接口:allocate函數(shù)、deallocate函數(shù),使得外部無需關心使用的是幾級內(nèi)存配置器。另外simple_alloc類將外部所需的對象個數(shù)轉(zhuǎn)換為字節(jié)。如下。

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

template <typename T, typename Alloc>class simple_alloc
{public:        static T *allocate(size_t n) // 個數(shù)        {                return n == 0 ? 0 : (T*)Alloc::allocate(n * sizeof(T)); // 將個數(shù)轉(zhuǎn)換為字節(jié)        }        static T *allocate(void)
        {                return (T*)Alloc::allocate(sizeof(T));
        }        static void deallocate(T *p, size_t n) // 個數(shù)        {                if (n != 0)
                        Alloc::deallocate(p, n * sizeof(T));
        }        static void deallocate(T *p)
        {
                Alloc::deallocate(p, sizeof(T));
        }
};

大學生就業(yè)培訓,高中生培訓,在職人員轉(zhuǎn)行培訓,企業(yè)團訓

 

一款簡易版STL的實現(xiàn),項目地址:https://github.com/zinx2016/MiniSTL/tree/master/MiniSTL

 

http://www.cnblogs.com/zxiner/p/7197054.html