Implement memory allocator and deallocator using the mechanism in SGI STL

来源:互联网 发布:网络胜利组 百度网盘 编辑:程序博客网 时间:2024/06/09 17:14

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.

原创粉丝点击