4.4
Chunks(大塊內存)
每個Chunk對象包含并管理一大塊內存,其中包含固定數量的區塊。你可以在構造期間設定區塊的大小和數量。你可以從chunk中分配和歸還區塊。一旦chunk之中沒有剩余區塊,分配函數便傳回0。chunk定義如下:
// Nothing is private - Chunk is a Plain Old Data (POD) structure
// structure defined inside FixedAllocator
// and manipulated only by it
struct Chunk{
void Init(std::size_t blockSize, unsigned char blocks);
void Release();
void* Allocate(std::size_t blockSize);
void Deallocate(void* p, std::size_t blockSize);
unsigned char* pData_;
unsigned char
firstAvailableBlock_,
blocksAvailable_;
};
除了一個指針指向被管理內存本身,firstAvailableBlock_保存chunk內第一個可用區塊的索引,blocksAvailable_保存chunk內可用區塊總數。
Chunk的接口非常簡單。Init()用于初始化,Release()用來釋放。Allocate()用來分配,Deallocate()用來歸還。Chunk不保存區塊的大小,它沒有構造函數、析構函數和賦值運算符,定義自己的copy語義會損及上一層的效率——上一層將chunk置于一個vector。
chunk的結構反應出設計中一個重要折衷。blocksAvailable_和firstAvailableBlock_都是unsigned char型別,因此一個chunk無法擁有255個以上的區塊。
另外我們利用“未被使用的區塊”的第一個bytes來存放下一個“未被使用的區塊”的索引號。這樣我們就擁有了一個單向鏈表。無需占用內存。
初始化函數如下:
void Chunk::Init(std::size_t blockSize, unsigned char blocks){
pData_ = new unsigned char[blockSize * blocks];
firstAvailableBlock_ = 0;
blocksAvailable_ = blcoks;
unsigned char i = 0;
unsigned char* p = pData_;
for(; i != blocks; p += blockSize){
*p = ++i;
}
}
我們來看一下將區塊數量限制在unsigned char的大小(0~255)的原因。假如我們使用一個較大型別,比如unsigned short(0~65535),我們將遭遇兩個問題,一個小問題,一個大問題。
小問題是我們無法分配小于sizeof(unsigned
short)的區塊,這令人尷尬,因為我們正在建立一個小型對象分配器。(這里指的是,對于1 byte區塊大小的chunk,由于索引號有2 bytes,無法建立內嵌的單向鏈表,cuigang)。
大問題是齊位(alignment)問題。假設你為一個5 bytes區塊建立一個專屬分配器。這種情況下如果想將“指向如此一個5 bytes區塊”的指針轉換為unsigned int(原文如此,估計作者是想說從一個起始為奇地址的區塊提領(dereference)一個偶字節變量會導致異常的問題,這幾個函數中都有提領的動作。cuigang),會造成不確定行為。
unsigned
char類型可以簡單的解決這個問題。它大小為1,無齊位問題。只不過我們將無法分配多于255的區塊。但這個我們是可以接受的。
分配函數Allocate()是典型的list操作。
void* Chunk::Allocate(std::size_t blockSize){
if(!blocksAvailable_) return 0;
unsigned char* pResult =
pData_ + (firstAvailableBlock_ * blockSize);
firstAvailableBlock_ = *pResult;
--blocksAvailable_;
return pResult;
};
這個函數成本很小,不需要查找動作,在常數事件內完成,而不像系統分配需要線性時間。
歸還函數Deallocate()行為相反,這里要注意,由于Chunk對區塊大小一無所知,所以你必須將區塊大小當作參數傳入,同時注意里面很多的異常處理來避免你將錯誤指針傳入:
void Chunk::Deallocate(void* p, std::size_t blockSize){
assert(p >= pData_);
unsigned char* toRelease = static_cast<unsigned char*>(p);
assert((toRelease - pData_) % blockSize == 0);
*toRelease = firstAvailableBlock_;
firstAvailableBlcok_ = static_cast<unsigned char>(
(toRelease - pData_) / blockSize);
assert(firstAvailableBlock == (toRelease - pData_) / blockSize);
++blocksAvailable_;
};