二、定長內存池的設計
設計一個定長的內存池,這個內存池的定長在于,當剩余空間使用完畢后,總是開辟相同長度的新空間來使用。我們會使用到一個指針來切割劃分大空間為小空間。大空間是內存池向系統申請的內存大小,而小空間是程序向該內存池申請的內存大小。由于程序向內存池申請存放空間的類型不同,這個小空間的大小也由需要存放的類型決定大小。
實際上指針只有一個,
_freeList
后面的空間理論上是連起來的,_freeList
在被申請空間后更新,相當于鏈表的頭插頭刪。
#include <iostream>
#include <vector>
#ifdef _WIN32
#include <Windows.h>
#endif
//從堆上按頁申請空間
inline static void* SystemAlloc(size_t page)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, page << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#endif
#ifdef __linux__size_t size = page << 13;void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);#endifif (ptr == nullptr){throw std::bad_alloc();}return ptr;
}template<class T>
class FixedPool
{T* New(){T* object = nullptr;//優先使用還回來的空間if (_freeList){void* next = *((void**)_freeList);object = (T*)_freeList;_freeList = next;}//如果大空間不足if (_restBytes < sizeof(T)){_restBytes = 128 * 1024; //一次申請128Kb的空間_memory = (char*)SystemAlloc(_restBytes >> 13); //128Kb右移13位相當于16頁if (_memory == nullptr){throw std::bad_alloc();}}//從申請的大空間中截一塊出來object = (T*)_memory;size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objSize;_restBytes -= objSize;//定位new,顯示調用T的構造函數new(obj)T;return obj;}void Delete(T* obj){obj->~T();*(void**)obj = _freeList;_freeList = obj;//把空間還回來}
private:char* _memory = nullptr; //char類型為1字節大小方便申請任意大小的內存size_t _restBytes = 0; //記錄大內存在切分后的剩余比特數void* _freeList = nullptr;
};
*(void**)
的強制類型轉換是在兼容 32 位和 64 位,使其不會因為指針大小不同而程序出錯,也不用為了兼容 32 位和 64 位使用條件編譯。
size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
這行,要求開的空間必須比指針大,因為我們會用歸還回來的空間存放,_freeList
來指向下一塊空間,如果 T
小于指針的大小,就有可能存不進 _freeList
。