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