STL is an amazing utility related to C++. In most cases, the vectors and algorithms can work very well, i.e., effectively and efficiently. The management of memory, allocation and deallocation, is well designed to achieve that goal. In this article, an implementation of memory allocator and deallocator, which is based on the mechanism used in SGI STL, is given. Following the code, you will understand how it works.
There are two kinds of allocators, one dealing with large block allocation, the other dealing with small block allocation. And, one tricky thing is that they both use malloc, realloc and free, which are in C library, to do the real memory allocation. The reason is that it is more direct and more efficient. The following code shows the mechanism behind them (in SGI STL, the whole process is arranged in hierarchical function calls which are more clear, while here, in order to show it as a whole, sort of design philosophy, I combined them together).
//my_allocator.h
#define _MY_ALLOCATOR_H_
#include < cstddef >
// oom: out of memory
typedef void ( * oom_handler)( void );
class MallocAllocator {
public:
static void * Allocate(size_t);
static void Deallocate(void *, size_t);
// old returned
static oom_handler GetOOMHandler();
// new set, old returned
static oom_handler SetOOMHandler(oom_handler);
private:
//size_t blk_size;
static oom_handler malloc_allocator_oom_handler;
} ;
enum { _ALIGN = 8 } ;
enum { _MAX_BYTES = 128 } ;
enum { _FREELISTS_NUMBER = 16 } ;
enum { _BLOCK_NUMBER = 20 } ;
class DefaultAllocator {
public:
static void * Allocate(size_t);
static void Deallocate(void *, size_t);
private:
union obj {
union obj * next; // node in free list
char data[1]; // real data
};
// round the size up to the ceiling multiple of 8
static size_t RoundUp(size_t);
// find the appropriate free list for the given size
static size_t FreelistIndex(size_t);
// fill a new free list
static obj * FillFreelist(size_t);
// allocate a new memory block
static char * AllocateBlock(size_t, size_t &);
// manage all the free lists
static obj * volatile _freelists[_FREELISTS_NUMBER];
static char * _pool_start; // start of the memory pool
static char * _pool_end; // end of the memory pool
static size_t _pool_size; // size of the memory pool
} ;
#endif
//my_allocator.cpp
#include < cstdlib >
#include < new >
#include < iostream >
using namespace std;
// class MallocAllocator
oom_handler MallocAllocator::malloc_allocator_oom_handler = 0 ;
void * MallocAllocator::Allocate(size_t size) {
void * result = malloc(size);
// deal with the condition of out of memory
if(result == 0) {
oom_handler current_handler;
// infinite loop
while(1) {
current_handler = GetOOMHandler();
if(current_handler == 0)
throw std::bad_alloc(); // no oom handler
else {
// do something, e.g., throw an exception, or free some space, or set a new oom_handler
(*current_handler)();
// try to allocate again
result = malloc(size);
if(result != 0)
return result;
}
}
}
else
return result;
}
void MallocAllocator::Deallocate( void * dead_obj, size_t size) {
free(dead_obj);
}
oom_handler MallocAllocator::GetOOMHandler() {
return malloc_allocator_oom_handler;
}
oom_handler MallocAllocator::SetOOMHandler(oom_handler new_oom_handler) {
oom_handler old_oom_handler = malloc_allocator_oom_handler;
malloc_allocator_oom_handler = new_oom_handler;
return old_oom_handler;
}
// end of MallocAllocator
// class DefaultAllocator
char * DefaultAllocator::_pool_start = 0 ;
char * DefaultAllocator::_pool_end = 0 ;
size_t DefaultAllocator::_pool_size = 0 ;
DefaultAllocator::obj * volatile DefaultAllocator::_freelists[_FREELISTS_NUMBER] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
} ;
size_t DefaultAllocator::RoundUp(size_t size) {
// if size is already a multiple of _ALIGN, it will not change
return (size + _ALIGN - 1) & ~(_ALIGN - 1);
}
size_t DefaultAllocator::FreelistIndex(size_t size) {
// size will never be zero
return (size + _ALIGN - 1) / _ALIGN - 1;
}
void * DefaultAllocator::Allocate(size_t size) {
// deal with large memory block request
if(size > _MAX_BYTES)
return MallocAllocator::Allocate(size);
// deal with small memory block request
size_t blk_size = RoundUp(size);
size_t list_index = FreelistIndex(blk_size);
obj * list_head = _freelists[list_index];
obj * result;
// the free list is not empty
if(list_head != 0) {
result = list_head;
cout << "list_head: " << list_head << ", next: " << list_head->next << endl;
_freelists[list_index] = list_head->next;
cout << "list_head: " << list_head << ", next: " << list_head->next << endl;
//return list_head;
}
// fill a new free list
else {
//cout << "1" << endl;
size_t blk_num = _BLOCK_NUMBER;
// check the pool
size_t free_size = _pool_end - _pool_start;
size_t request_size = blk_num * blk_size;
// pool is enough for at least 1 block
if(free_size >= blk_size) {
char * chunk = _pool_start;
// pool is enough for more than BLOCK_NUMBER blocks
if(request_size <= free_size) {
_pool_start += request_size;
}
// pool is less than BLOCK_NUMBER blocks
else {
blk_num = free_size / blk_size;
_pool_start += blk_num * blk_size;
}
// if at least 2 blocks, link them together
if(blk_num > 1) {
obj * next_obj, * curr_obj;
next_obj = /*static_cast<obj *>*/(obj *)(chunk + blk_size);
//cout << "head: " << next_obj << endl; //10 //correct
for(int i = 1; i < blk_num - 1; ++i) {
curr_obj = next_obj;
//next_obj = static_cast<obj *>(static_cast<char *>(next_obj) + blk_size);
next_obj = (obj *)((char *)(next_obj) + blk_size);
curr_obj->next = next_obj;
//cout << "head: " << next_obj << endl; //correct
}
next_obj->next = 0;
//_freelists[list_index] = static_cast<obj *>(chunk + blk_size);
_freelists[list_index] = (obj *)(chunk + blk_size);
//cout << "haha: " << _freelists[list_index] << endl;
}
//result = static_cast<obj *>(chunk);
result = (obj *)(chunk);
}
// pool is less than 1 block
else {
// put the fragment into the appropriate free list
if(free_size > 0) {
int index = FreelistIndex(free_size);
//(static_cast<obj *>(_pool_start))->next = _freelists[index];
((obj *)_pool_start)->next = _freelists[index];
//_freelists[index] = static_cast<obj *>(_pool_start);
_freelists[index] = (obj *)_pool_start;
}
// allocate new pool
request_size = (request_size << 1) + RoundUp((_pool_end - _pool_start) >> 4);
_pool_start = static_cast<char *>(malloc(request_size));
if(_pool_start != 0) {
_pool_end = _pool_start + request_size;
//cout << "_pool_start: " << (void *)_pool_start << endl;
return Allocate(size);
}
else {
// check the existing free lists which are large enough
for(int i = list_index + 1; i < _FREELISTS_NUMBER; ++i) {
if(_freelists[i] != 0) {
//_pool_start = static_cast<char *>(_freelists[i]);
_pool_start = (char *)(_freelists[i]);
//_pool_end = static_cast<char *>(_freelists[i]) + i * _ALIGN;
_pool_end = (char *)(_freelists[i]) + i * _ALIGN;
_freelists[i] = _freelists[i]->next;
return Allocate(size);
}
}
}
_pool_end = 0;
_pool_start = static_cast<char *>(MallocAllocator::Allocate(request_size));
_pool_end = _pool_start + request_size;
return Allocate(size);
}
}
return result;
}
void DefaultAllocator::Deallocate( void * dead, size_t size) {
// deal with large memory block
if(size > _MAX_BYTES)
MallocAllocator::Deallocate(dead, size);
// deal with small memory block
size_t index = FreelistIndex(size);
obj * freelist = _freelists[index];
((obj *)dead)->next = freelist;
_freelists[index] = (obj *)dead;
}
// end of DefaultAllocator
These two classes are wrappered by a class MemoryPool.
//MemoryPool.h
#define _MEMORY_POOL_H
#include < cstddef >
#include " my_allocator.h "
class MemoryPool {
public:
MemoryPool(size_t);
~MemoryPool();
void * Alloc(size_t);
void Free(void *, size_t);
public:
size_t obj_size, block_size;
} ;
#endif
//MemoryPool.cpp
#include < iostream >
using namespace std;
MemoryPool::MemoryPool(size_t size)
: obj_size(size), block_size( 16 )
{}
MemoryPool:: ~ MemoryPool() {}
void * MemoryPool::Alloc(size_t size) {
DefaultAllocator::Allocate(size);
}
void MemoryPool::Free( void * dead_object, size_t size) {
if(dead_object == 0)
return;
DefaultAllocator::Deallocate(dead_object, size);
}
AirplaneWithPool is a small class sized 4 bytes for testing.
//AirplaneWithPool.h
#define _AIRPLANE_WITH_POOL_H
#include " MemoryPool.h "
#include < cstddef >
class AirplaneReq {
//many data and function members
} ;
class AirplaneWithPool {
public:
static void * operator new(size_t);
static void operator delete(void *, size_t);
public:
AirplaneReq * req;
static MemoryPool mem_pool;
} ;
#endif
//AirplaneWithPool.cpp
#include " MemoryPool.h "
MemoryPool AirplaneWithPool::mem_pool( sizeof (AirplaneWithPool));
void * AirplaneWithPool:: operator new (size_t size) {
return mem_pool.Alloc(size);
}
void AirplaneWithPool:: operator delete( void * dead_object, size_t size) {
mem_pool.Free(dead_object, size);
}
Now is the tesing code.
//main.cpp
#include < iostream >
using namespace std;
int main() {
AirplaneWithPool * apwp0 = new AirplaneWithPool();
cout << "apwp0: " << apwp0 << endl;
AirplaneWithPool * apwp1 = new AirplaneWithPool();
cout << "apwp1: " << apwp1 << endl;
AirplaneWithPool * apwp2 = new AirplaneWithPool();
cout << "apwp2: " << apwp2 << endl;
delete apwp1;
AirplaneWithPool * apwp3 = new AirplaneWithPool();
cout << "apwp3: " << apwp3 << endl;
delete apwp3;
AirplaneWithPool * apwp4 = new AirplaneWithPool();
cout << "apwp4: " << apwp4 << endl;
return 0;
}
The running results are as follows.
//printed in terminal
apwp0: 0x804b008
apwp1: 0x804b010
apwp2: 0x804b018
apwp3: 0x804b010
apwp4: 0x804b010
We can see every object ocupies 8 bytes although the real size of it is only 4 bytes. The reason is in SGI STL, the size of requested block is always rounded up to a multiple of 8 in bytes.