Now the free function. The thing is worth noticing in this function is that it will try to merge any free block before and after the block we are trying to free. This grants us that we won't have any free block followed (or preceeded) by another free block.
inline void StandardMemoryPool::free(void* ptr)
{
if(!ptr) return;
Chunk* block = (Chunk*)( (BYTE*)ptr - sizeof(Chunk) );
ASSERT(block->m_free == false, "This block is already free");
if(block->m_free) return;
The pointer we are given is pointing to user data. We need to move it back of the size of the header to have the Chunk structure.
DWORD fullBlockSize = block->m_userdataSize + sizeof(Chunk) +
(m_boundsCheck == 1 ? s_boundsCheckSize * 2 : 0);
m_freePoolSize += block->m_userdataSize;
Chunk* headBlock = block;
Chunk* prev = block->m_prev;
Chunk* next = block->m_next;
headBlock will be the head of the free block. prev and next are pointers relative toheadBlock. Initially headBlock is the block we want to free but if the previous block is also free then we set this one to be the headBlock and we increase the size to cover both blocks.
if(block->m_prev && block->m_prev->m_free)
{
headBlock = block->m_prev;
prev = block->m_prev->m_prev;
next = block->m_next;
fullBlockSize += m_boundsCheck == 1 ?
block->m_prev->m_userdataSize +
sizeof(Chunk) + s_boundsCheckSize * 2 : block->m_prev->m_userdataSize +
sizeof(Chunk);
if(block->m_next)
{
block->m_next->m_prev = headBlock;
if( block->m_next->m_free )
{
next = block->m_next->m_next;
if(block->m_next->m_next)
block->m_next->m_next->m_prev = headBlock;
fullBlockSize +=m_boundsCheck == 1 ?
block->m_next->m_userdataSize+
sizeof(Chunk) +
s_boundsCheckSize * 2 :
block->m_next->m_userdataSize +
sizeof(Chunk);
}
}
}
If the previous one is not free while the next one is not used then we include it in the current headBlock.
else
if(block->m_next && block->m_next->m_free)
{
headBlock = block;
prev = block->m_prev;
next = block->m_next->m_next;
fullBlockSize +=m_boundsCheck == 1 ?
block->m_next->m_userdataSize+
sizeof(Chunk) + s_boundsCheckSize * 2 :
block->m_next->m_userdataSize +
sizeof(Chunk);
}
Now that we have modified all the pointers we create the free block itself.
BYTE* freeBlockStart = (BYTE*)headBlock;
if(m_trashOnFree)
memset( m_boundsCheck == 1 ? freeBlockStart -
s_boundsCheckSize : freeBlockStart,
s_trashOnFreeSignature, fullBlockSize );
DWORD freeUserDataSize = fullBlockSize - sizeof(Chunk);
freeUserDataSize = (m_boundsCheck == 1) ? freeUserDataSize -
s_boundsCheckSize * 2 : freeUserDataSize;
Chunk freeBlock(freeUserDataSize);
freeBlock.m_prev = prev;
freeBlock.m_next = next;
freeBlock.write(freeBlockStart);
if(m_boundsCheck)
{
memcpy( freeBlockStart - s_boundsCheckSize, s_startBound,
s_boundsCheckSize );
memcpy( freeBlockStart + sizeof(Chunk) + freeUserDataSize,
s_endBound, s_boundsCheckSize );
}
}
That's it. The hard part is done.
Now, since we want to handle several pools, we may want to have an XML file to store all this information. This means that we need a manager class and some XML parsing stuff. To parse the XML I've used a small opensource parser called TinyXML. It's included in the compressed file.
The memory pool manager class is simply a Singleton that contains a hashtable with all the pools. When constructed the manager will search for "pool.xml" and will generate all the pools as specified in that file. We can then get the pool by name as follow:
MemoryPool* pool = MemoryPoolManager::it().getPool(name);
To make it easy to use we probably want to override new and delete. This is done in Allocation.h. This file also contains some useful defines that allow us to simply invoke a macro to allocate and deallocate as follows:
MyClass* object = NEW(POOL("MyPool")) MyClass(param1, param2);
DELETE(POOL("MyPool"), object);
That's it for now. I'm considering if it is worth to split this tutorial in two blocks to be more detailed, but I'm sure that if you are interested in memory management you are good enough to dig out any information from the source! :)
|