STL—內存的配置與釋放

上一篇咱們介紹了STL對象的構造與析構,這篇介紹STL內存的配置與釋放。
STL有兩級空間配置器,默認是使用第二級。第二級空間配置器會在某些狀況下去調用第一級空間配置器。空間配置器都是在allocate函數內分配內存,在deallocate函數內釋放內存。
 
第一級空間配置器
 
第一級配置器只是對malloc函數和free函數的簡單封裝,在allocate內調用malloc,在deallocate內調用free。同時第一級配置器的oom_malloc函數,用來處理malloc失敗的狀況。以下所示:
allocate對malloc函數簡單封裝 :
static void *allocate(size_t n)
{
          void *result = malloc(n);
           if (NULL == result)
                    result = oom_malloc(n);
           return result;
}

 

deallocate對free函數簡單封裝 :html

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

 

oom_malloc調用外部提供的malloc失敗處理函數,而後從新試着再次調用malloc。重複執行此過程,直到malloc成功爲止 : git

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;
        }
}

 

 

第二級空間配置器github

第一級配置器直接調用malloc和free帶來了幾個問題:
1.內存分配/釋放的效率低。2. 當配置大量的小內存塊時,會致使內存碎片比較嚴重。3. 配置內存時,須要額外的部分空間存儲內存塊信息,因此配置大量的小內存塊時,還會致使額外內存負擔。
 
第二級配置器維護了一個自由鏈表數組,每次須要分配內存時,直接從相應的鏈表上取出一個內存節點就完成工做,效率很高。
 
自由鏈表數組
自由鏈表數組其實就是個指針數組,數組中的每一個指針元素指向一個鏈表的起始節點。數組大小爲16,即維護了16個鏈表,鏈表的每一個節點就是實際的內存塊,相同鏈表上的內存塊大小都相同,不一樣鏈表的內存塊大小不一樣,從8一直到128。以下所示,obj爲鏈表上的節點,free_list就是鏈表數組。
//自由鏈表
union obj
{
          union obj *free_list_link;
          char data[1];
};
//自由鏈表數組
static obj *volatile free_list[__NFREELISTS];

 

內存分配
allocate函數內先判斷要分配的內存大小,若大於128字節,直接調用第一級配置器,不然根據要分配的內存大小從16個鏈表中選出一個鏈表,取出該鏈表的第一個節點。若相應的鏈表爲空,則調用refill函數填充該鏈表。以下:
template <bool threads>
void *__default_alloc<threads>::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 (result == NULL)
        {
                //第n號鏈表無內存塊,則準備從新填充該鏈表
                void *r = refill(ROUND_UP(n));
                return r;
        }
        *my_free_list = result->free_list_link;
        return result;
}

 

填充鏈表
若allocate函數內要取出節點的鏈表爲空,則會調用refill函數填充該鏈表。
refill函數內會先調用chunk_alloc函數從內存池分配一大塊內存,該內存大小默認爲20個鏈表節點大小,當內存池的內存也不足時,返回的內存塊節點數目會不足20個。接着refill的工做就是將這一大塊內存分紅20份相同大小的內存塊,並將各內存塊鏈接起來造成一個鏈表。以下:
template <bool threads>
void *__default_alloc<threads>::refill(size_t n)
{
        int nobjs = __NOBJS;
        char *chunk = chunk_alloc(n, nobjs);  //從內存池獲取內存
        if (nobjs == 1)  //只能分配一塊,則直接返回給調用者
                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++)  //將剩下的區塊添加進鏈表
        {
                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;
}

 

內存池
chunk_alloc函數內管理了一塊內存池,當refill函數要填充鏈表時,就會調用chunk_alloc函數,從內存池取出相應的內存。
在chunk_alloc函數內首先判斷內存池大小是否足夠填充一個有20個節點的鏈表,若內存池足夠大,則直接返回20個內存節點大小的內存塊給refill。以下:
        if (size_left >= total_size)  //內存池剩餘空間知足需求
        {
                result = start_free;
                start_free += total_size;
                return result;
        }

 

若內存池大小沒法知足20個內存節點的大小,但至少知足1個內存節點,則直接返回相應的內存節點大小的內存塊給refill;數組

        else if (size_left >= size)  //剩餘空間不能所有知足,但至少知足一塊
        {
                nobjs = size_left / size;
                result = start_free;
                start_free += nobjs * size;
                return result;

 

若內存池連1個內存節點大小的內存塊都沒法提供,則chunk_alloc函數會將內存池中那一點點的內存大小分配給其餘合適的鏈表,而後去調用malloc函數分配的內存大小爲所需的兩倍。若malloc成功,則返回相應的內存大小給refill;若malloc失敗,會先搜尋其餘鏈表的可用的內存塊,添加到內存池,而後遞歸調用chunk_alloc函數來分配內存,若其餘鏈表也無內存塊可用,則只能調用第一級空間配置器,由於第一級空間配置器有malloc失敗的出錯處理函數,最終的但願只能寄託在那裏了。函數

以下是整個chunk_alloc函數:
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)  //內存池剩餘空間知足需求
        {
                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  //連一個區塊都沒法知足
        {
                if (size_left > 0)  //將殘餘內存分配給其餘合適的鏈表
                {
                        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;
                        //調用第一級配置器
                        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);
        }
}

 

內存釋放
第二級配置器的deallocate函數並不會直接釋放內存塊。當內存塊大小大於128字節時纔會直接釋放,不然會將內存塊回收到相應的鏈表當中。以下:
void __default_alloc<threads>::deallocate(void *p, size_t n)
{
        //大於__MAX_BYTES,則釋放該內存
        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->free_list_link = *my_free_list;
        *my_free_list = q;
}

 

內存對外接口spa

STL對外提供了一個simple_alloc類,該類提供統一的接口:allocate函數、deallocate函數,使得外部無需關心使用的是幾級內存配置器。另外simple_alloc類將外部所需的對象個數轉換爲字節。以下。指針

template <typename T, typename Alloc>
class simple_alloc
{
public:
        static T *allocate(size_t n) // 個數
        {
                return n == 0 ? 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 (n != 0)
                        Alloc::deallocate(p, n * sizeof(T));
        }

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

 

(全文完)code

附:
一款簡易版STL的實現,項目地址: https://github.com/zinx2016/MiniSTL/tree/master/MiniSTL
相關文章
相關標籤/搜索