STL空間配置器解析和實現

  STL空間配置器的強大和借鑑做用不言而喻,查閱資料,發現了 Dawn_sf已經對其有了極其深刻和詳細的描述,因此決定偷下懶借用其內容,只提供本身實現STL空間配置器的源碼,具體解析內容參考:
(一) STL — 淺析一級空間配置器
(二) STL — 淺析二級空間配置器

1. 一級空間配置器實現

1.1 接口

// 徹底解析STL空間配置器
/***** 一級配置區 ****************************/ 
// 1. 採用mallo/relloc/free申請和釋放內存
// 2. 處理內存申請失敗的處理
//      (1)設置set_new_handle,若爲NULL拋出__THROW_BAD_ALLOC;
//      (2)嘗試重複申請
/**********************************************/
#pragma once

class CFirstLevelAlloc;
class CSecondLevelAlloc;

#ifdef _CHUNK_ALLOC
typedef CFirstLevelAlloc SelfAlloc;
#else
typedef CSecondLevelAlloc SelfAlloc;
#endif

typedef void(*CallBackFunc)();
class CFirstLevelAlloc
{
public:
    CFirstLevelAlloc();
    
    static CallBackFunc m_CallBackFunc;
    static void* Allocate(size_t nSize);
    static void* Allocate(void *p, size_t nSize);
    static void Deallocate(void *p, size_t nSize = 0);
    static void SetCallBackHandle(CallBackFunc cb);

private:
    static void* ReAllocate(size_t nSize);
    static void* ReAllocate(void *p, size_t nSize);
};

enum {ALIGN = 8};  // 小型區塊的上調邊界
enum {MAX_BYTES = 128}; // 小型區塊的上限
enum {FREELISTNUM = MAX_BYTES/ALIGN}; // free-lists個數

// 空閒內存鏈表結構
union FreeList
{
    union FreeList *pFreeList;
    char client_data[1];
};

1.2 實現

#include "stdio.h"
#include "alloc_define.h"
#include <stdlib.h>
#include <iostream>
using namespace std;

CallBackFunc CFirstLevelAlloc::m_CallBackFunc = NULL;
CFirstLevelAlloc::CFirstLevelAlloc()
{
    
}

void* CFirstLevelAlloc::Allocate(size_t nSize)
{
    void *result = malloc(nSize);
    if (NULL == result)
    {
        result = ReAllocate(nSize);
    }
    return result;
}

void* CFirstLevelAlloc::Allocate(void *p, size_t nSize)
{
    void *result = realloc(p, nSize);
    if (NULL == result)
    {
        result = ReAllocate(p, nSize);
    }
    return result;
}


void* CFirstLevelAlloc::ReAllocate(size_t nSize)
{
    while (1)
    {
        if (NULL == m_CallBackFunc)
        {
            cout << "bad alloc!" << endl;
            return NULL;
        }
        else
        {
            m_CallBackFunc();
            void *result = Allocate(nSize);
            if (result)
            {
                return result;
            }
        }
    }
}

void* CFirstLevelAlloc::ReAllocate(void *p, size_t nSize)
{
    while (1)
    {
        if (NULL == m_CallBackFunc)
        {
            cout << "bad alloc!" << endl;
            return NULL;
        }
        else
        {
            m_CallBackFunc();
            void *result = Allocate(p, nSize);
            if (result)
            {
                return result;
            }
        }
    }
}

void CFirstLevelAlloc::Deallocate(void *p, size_t nSize)
{
    free(p);
}
void CFirstLevelAlloc::SetCallBackHandle(CallBackFunc cb)
{
    m_CallBackFunc = cb;
}

2. 二級空間配置器實現

2.1 接口

class CSecondLevelAlloc
{
public:
    CSecondLevelAlloc();
    static void* Allocate(size_t nSize);
    static void Deallocate(void *p, size_t nSize);
    static void SetCallBackHandle(CallBackFunc cb);

private:
    static size_t FreeListIndex(int nBytes); // 根據區塊大小獲得freelist索引
    static size_t Round_Up(int nBytes);  // 將bytes按內存對齊上調至ALIGN的倍數
    static char *ChunkAlloc(size_t nSize, int& nObjs);
    static void* Refill(size_t nSize);
    
private:
    static FreeList *m_pFreeList[FREELISTNUM];
    static char *m_pStartFree;
    static char *m_pEndFree;
    static size_t m_nHeapSize;
};

2.2 實現

FreeList* CSecondLevelAlloc::m_pFreeList[FREELISTNUM] = { 0 };
char* CSecondLevelAlloc::m_pStartFree = NULL;
char* CSecondLevelAlloc::m_pEndFree = NULL;
size_t CSecondLevelAlloc::m_nHeapSize = 0;
CSecondLevelAlloc::CSecondLevelAlloc()
{
}


void* CSecondLevelAlloc::Allocate(size_t nSize)
{
    // 首先判斷nSize,若大於128則調用一級配置器,不然調用二級配置器
    if (nSize > (size_t)MAX_BYTES)
    {
        cout << "調用1級配置器配置內存空間,空間大小:" << nSize << endl;
        return (CFirstLevelAlloc::Allocate(nSize));
    }

    cout << "調用2級配置器配置內存空間,空間大小:" << nSize << endl;
    FreeList **pFreeList = m_pFreeList + FreeListIndex(nSize);
    if (*pFreeList == NULL)
    {
        return Refill(Round_Up(nSize));
    }

    FreeList *p = *pFreeList;
    *pFreeList = p->pFreeList;
    return p;
}

void CSecondLevelAlloc::Deallocate(void *p, size_t nSize)
{
    // 首先判斷nSize,若大於128則調用一級配置器,不然調用二級配置器
    if (nSize > MAX_BYTES)
    {
        cout << "調用1級配置器釋放內存空間,空間大小:" << nSize << endl;
        return CFirstLevelAlloc::Deallocate(p);
         
    }

    cout << "調用2級配置器釋放內存空間,空間大小:" << nSize << endl;
    FreeList **pFreeList = m_pFreeList + FreeListIndex(Round_Up(nSize));
    ((FreeList*)p)->pFreeList = *pFreeList;
    *pFreeList = (FreeList*)p;
}
size_t CSecondLevelAlloc::FreeListIndex(int nBytes)
{
    return ((nBytes + ALIGN) / ALIGN - 1);
}
size_t CSecondLevelAlloc::Round_Up(int nBytes)
{
    return ((nBytes + ALIGN - 1) & (~(ALIGN - 1)));
}
char* CSecondLevelAlloc::ChunkAlloc(size_t nSize, int& nObjs)
{
    char *pResult = NULL;
    size_t nTotalBytes = nSize * nObjs;
    size_t nBytesLeft = m_pEndFree - m_pStartFree;
    if (nBytesLeft >= nTotalBytes)
    {
        // 內存池剩餘空間徹底知足需求量
        pResult = m_pStartFree;
        m_pStartFree += nTotalBytes;
        return pResult;
    }
    else if (nBytesLeft >= nSize)
    {
        // 內存池剩餘空間不能徹底知足需求量,但足夠供應一個或一個以上的區塊
        nObjs = nBytesLeft / nSize;
        pResult = m_pStartFree;
        m_pStartFree += (nObjs * nSize);
        return pResult;
    }
    else
    {
        // 內存池剩餘空間連一個區塊的大小都沒法提供,就調用malloc申請內存,新申請的空間是需求量的兩倍
        // 與隨着配置次數增長的附加量,在申請以前,將內存池的殘餘內存回收
        size_t nBytesToGet = 2 * nTotalBytes + Round_Up(m_nHeapSize >> 4);
        // 如下試着讓內存池中的殘餘零頭還有價值
        if (nBytesLeft > 0)
        {
            // 內存池內還有一些零頭,先配給適當的freelist
            // 首先尋找適當的freelist
            FreeList *pFreeList = m_pFreeList[FreeListIndex(nBytesLeft)];
            // 調整freelist,將內存池中的殘餘空間編入
            ((FreeList*)m_pStartFree)->pFreeList = pFreeList;
            pFreeList = (FreeList*)m_pStartFree;

        }
        // 配置heap空間
        m_pStartFree = (char *)malloc(nBytesToGet);
        if (NULL == m_pStartFree)
        {
            //若是沒有申請成功,若是free_list當中有比n大的內存塊,這個時候將free_list中的內存塊釋放出來.  
            //而後將這些內存編入本身的free_list的下標當中.調整nobjs.
            int i;
            FreeList **pFreeList, *p;
            for (i = nSize; i < MAX_BYTES; i += ALIGN)
            {
                pFreeList = m_pFreeList+FreeListIndex(i);
                p = *pFreeList;
                if (NULL != p)
                {
                    // freelist內尚有未用區塊
                    // 調整freelist以釋放未用區塊
                    *pFreeList = p->pFreeList;
                    m_pStartFree = (char *)p;
                    m_pEndFree = m_pStartFree + i;
                    // 調整本身,爲了修正nobjs
                    return (ChunkAlloc(nSize, nObjs));
                }
            }

            m_pEndFree = NULL; // 若是出現意外(山窮水盡,處處都沒有內存可用)
            // 調用1級配置器,看out-of-range機制能不能出點力
            m_pStartFree = (char*)CFirstLevelAlloc::Allocate(nBytesToGet);
        }

        m_nHeapSize += nBytesToGet;
        m_pEndFree = m_pStartFree + nBytesToGet;
        return (ChunkAlloc(nSize, nObjs));
    }
}

// 當freelist中沒有可用的區塊了時,就調用ReFill從新填充空間
// 新的空間將取自內存池,缺省爲20個新節點
// 但萬一內存池空間不足,得到的節點數可能小於20
void* CSecondLevelAlloc::Refill(size_t nSize)
{
    int nObjs = 20; // 默認每一個鏈表組右20個區塊
    char *pChunk = ChunkAlloc(nSize, nObjs);
    if (1 == nObjs)
    {
        // 若是得到一個區塊,這個區塊就分配給調用者,freelist無新節點
        return pChunk;
    }

    // 如有多餘的區塊,則將其添加到對應索引的freelist中
    FreeList **pFreeList = m_pFreeList + FreeListIndex(nSize);
    FreeList *pResult = (FreeList *)pChunk;  // 這一塊準備返回給客戶端

    FreeList *pCurrent = NULL; 
    FreeList *pNext = NULL;
    *pFreeList = pNext = (FreeList*)(pChunk + nSize);
    for (int i = 1; i < nObjs; i++)
    {
        pCurrent = pNext;
        pNext = (FreeList*)((int)pNext + nSize);
        pCurrent->pFreeList = pNext;
    }
    pCurrent->pFreeList = NULL;

    return pResult;
}

void CSecondLevelAlloc::SetCallBackHandle(CallBackFunc cb)
{
    CFirstLevelAlloc::m_CallBackFunc = cb;
}

3. 配置器標準接口

#pragma once

#include "alloc_define.h"
template<typename T, typename Alloc = SelfAlloc>
class CSimpleAlloc
{
public:
    static T* Allocate(size_t n)
    {
        if (n == 0)
        {
            return NULL;
        }
        return (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));
    }

    static void SetCallBackHandle(CallBackFunc cb)
    {
        Alloc::SetCallBackHandle(cb);
    }
};

4. 測試

#include "stdio.h"

#include<iostream>
using namespace std;

#include"stl_test.h"
#include "simple_alloc.h"
#include<vector>

void Func()
{
    cout << "調用回調函數處理內存不足的狀況" << endl;
    // 爲了防止死循環,該函數應該加上一個判斷條件若是它本次沒有清理出空間,那麼就拋出異常
}

template <class T, class Alloc = SelfAlloc>
class A
{
public:
    A() :m_ptr(NULL), m_nSize(0){}
    A(size_t nSize)
    {
        DataAllocator::SetCallBackHandle(Func);
        m_nSize = nSize;
        m_ptr = DataAllocator::Allocate(nSize);
        for (int i = 0; i < (int)nSize; i++)
        {
            m_ptr[i] = i;
            cout << m_ptr[i] << " ";
        }
        cout << endl;
    }
    ~A()
    {
        DataAllocator::Deallocate(m_ptr, m_nSize);
    }

private:
    T *m_ptr;
    size_t m_nSize;
    typedef CSimpleAlloc<T, Alloc> DataAllocator;
};
void main()
{
    A<int> a(11);
    A<int> b(50);
    a.~A();
    b.~A();
}

相關文章
相關標籤/搜索