Implement memory allocator and deallocator using the mechanism in SGI STL

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

#ifndef _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  " my_allocator.h "
#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
#ifndef _MEMORY_POOL_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  " MemoryPool.h "
#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
#ifndef _AIRPLANE_WITH_POOL_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  " AirplaneWithPool.h "
#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  " AirplaneWithPool.h "
#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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值