IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  AIX와 UNIX | 리눅스  >

C/C++ 프로젝트에 사용할 메모리 관리자 직접 구현하기 (한글)

developerWorks
Go to the previous page15 페이지 중 9 페이지Go to the next page

문서 옵션

샘플 코드


제안 및 의견
피드백

튜토리얼 평가

이 컨텐츠를 개선하기 위한 도움을 주십시오.


자유 리스트 기반 메모리 관리자

일반적인 프로그램에서는 자주 요청하는 메모리 블록 크기가 대개 몇 가지로 한정되어 있다. 이 절에서 구현할 메모리 관리자는 이러한 경험적 법칙을 활용한다. 메모리 관리자는 빈번히 사용하는 크기마다 자유 블록 주소 리스트를 관리하는데, 이를 기술 용어로 자유 리스트(free-list)라고 한다. 예를 들어 8, 16, 24, 32, 48, 64 바이트를 자주 요청하는 프로그램이 있다면, 메모리 관리자는 각 바이트 별로 자유 리스트 6개를 관리한다. 비트맵 메모리 관리자와 비슷하게, 메모리 관리자는 초반에 운영체제로부터 커다란 메모리 덩어리를 확보한다. 그런 다음, 메모리 관리자 내부에서 메모리 덩어리를 개별 리스트로 나누어 관리한다. 프로그램이 'm' 바이트를 요청하면, 메모리 관리자는 이 값을 'm'에 가장 가까운 블록 크기 'n'으로 변환한다. 그런 다음, 블록 크기가 'n'인 리스트에서 자유 블록을 찾아서 할당한 다음 프로그램에 돌려준다.

보호 바이트 간략 소개

'n' 바이트 블록은 객체만이 아니라 몇 가지 메타 정보도 저장한다. 각 블록은 끝 부분에 보호 바이트(guard byte)라는 추가 바이트를 포함한다. 보호 바이트는 힙 메모리에서 사용하는 개념이다. 일반적으로, memcpymemset 함수는 원래 할당 받은 메모리 범위를 벗어나서 힙 메모리를 손상시킬 수 있다. 그래서 많은 컴파일러는 메모리 할당 시 블록 앞뒤에 특수 문자, 즉 보호 바이트를 추가한다. 그런 다음, 해당 블록을 참조할 때 앞뒤에 특수 문자가 있는지 확인한다. 블록 앞뒤에 특수 문자가 없으면 프로그램이 어느 순간에 값을 덮어썼다고 가정한다. 즉 힙 메모리가 더 이상 정상 상태가 아니며 손상되었다고 가정한다. 이는 프로그래머를 괴롭히는 까다로운 메모리 버그를 찾아내는 좋은 방법이다. 다음 예제에서는 네 바이트 중 두 바이트를 보호 바이트로 사용한다. 세 번째 바이트는 객체 크기를 저장하고, 마지막 바이트는 블록 상태(자유/할당)를 나타내는 플래그를 저장한다.

자유 리스트 메모리 관리자 구현

앞서 설명했듯이, 메모리 관리자는 블록 크기 별로 블록 포인터 리스트를 여러 개 관리한다. 또한 운영체제에서 확보한 메모리 덩어리를 memoryPool 로 관리한다. MemoryManager 소멸자는 풀에 든 메모리 덩어리 전체를 해제하여 메모리 누수를 막는다. Listing 17 은 메모리 관리자 자료 구조다.


Listing 17. 자유 리스트 메모리 관리자를 위한 MemoryManager 클래스 정의
                    
class MemoryManager : public IMemoryManager 
  {
  private:
    VoidPtrList     Byte8PtrList;
    VoidPtrList     Byte16PtrList;
    VoidPtrList     Byte24PtrList;
    VoidPtrList     Byte32PtrList;
    VoidPtrList     Byte40PtrList;
    std::vector<void*>    MemoryPoolList;

    . . . . . . . . . //helper routines may go in here

  public: 
    MemoryManager( ) {}
    ~MemoryManager( ) {}
    void* allocate(size_t);
    void   free(void*);
  };    
				

예제 프로그램은 크기가 각각 16, 28, 32바이트인 세 가지 클래스를 사용한다. 보호 바이트까지 고려하면 24, 32, 40바이트 블록이 필요하다. 즉 Byte24PtrList , Byte32PtrList , Byte40PtrList 가 가장 자주 쓰이므로 코드 역시 세 리스트를 위주로 구현한다. 그러나 설계가 유연하므로 다른 블록 크기를 추가하기도 쉽다.

각 클래스에서 newdelete 연산을 중복 정의한다. 각 allocate / free 연산은 메모리 관리자 함수를 호출하여 메모리를 할당하고 해제한다. 메모리를 할당하고 해제하는 함수는 아래서 상세히 설명한다. 초기 객체 수는 1024개로 잡는다. 또한 예제에서 사용할 클래스 크기는 상수로 미리 정의한다. Listing 18 을 참조한다.


Listing 18. 메모리 관리자가 사용할 상수 정의
                    
  const int JOB_SCHEDULER_SIZE = sizeof(JobScheduler);
  const int COMPLEX_SIZE = sizeof(Complex);
  const int COORDINATE_SIZE = sizeof(Coordinate);
  const int POOL_SIZE = 1024; //number of elements in a single pool
            //can be chosen based on application requirements 

  const int MAX_BLOCK_SIZE = 36; //depending on the application it may change 
                //In above case it came as 36
				

위 상수는 메모리 관리자가 allocatefree 함수에서 사용한다.

메모리 할당

allocate 함수는 블록 크기를 지정한 인수 size를 보고 어느 자유 리스트에서 블록을 할당할지 결정한다. 그런 다음, 해당 리스트에 남아있는 자유 블록이 있는지 찾는다. 자유 리스트는 블록 자체가 아니라 블록 포인터만 저장한다는 사실에 주의한다(블록 자체는 MemoryPoolList 에 속한다).

자유 리스트가 비어 있다면, 운영체제에서 추가로 메모리 덩어리를 요청한 후 InitialiseByte24List , InitialiseByte32List , InitialiseByte40List 함수 중 하나로 초기화한다.

자유 블록이 있으면, 해당 블록을 할당 상태로 표시하고 보호 바이트를 설정한 후 블록 주소를 반환한다. 구현은 Listing 19 를 참조한다.


Listing 19. MemoryManager::allocate 정의
                    
void* MemoryManager::allocate(size_t size)
  {
  void* base = 0;
  switch(size)
    {
    case JOB_SCHEDULER_SIZE :  
      {
      if(Byte32PtrList.empty())
        {
        base = new char [32 * POOL_SIZE];
        MemoryPoolList.push_back(base);
        InitialiseByte32List(base);
        }
      void* blockPtr =  Byte32PtrList.front();
      *((static_cast<char*>(blockPtr)) + 30) = 32; //size of block set
      *((static_cast<char*>(blockPtr)) + 31) = 0; //block is no longer free
      Byte32PtrList.pop_front();
      return blockPtr;
      }         
    case COORDINATE_SIZE :  
      {
      if(Byte40PtrList.empty())
        {
        base = new char [40 * POOL_SIZE];
        MemoryPoolList.push_back(base);
        InitialiseByte40List(base);
        }
      void* blockPtr =  Byte40PtrList.front();
      *((static_cast<char*>(blockPtr)) + 38) = 40; //size of block set
      *((static_cast<char*>(blockPtr)) + 39) = 0; //block is no longer free
      Byte40PtrList.pop_front();
      return blockPtr;
      }         
    case COMPLEX_SIZE : 
      {
      if(Byte24PtrList.empty())
        {
        base = new char [24 * POOL_SIZE];
        MemoryPoolList.push_back(base);
        InitialiseByte24List(base);
        }
      void* blockPtr =  Byte24PtrList.front();
      *((static_cast<char*>(blockPtr)) + 22) = 24; //size of block set
      *((static_cast<char*>(blockPtr)) + 23) = 0; //block is no longer free
      Byte24PtrList.pop_front();
      return blockPtr;
      }
    default : break;
    }
  return 0;
  }
				

메모리 해제

free 함수는 해제할 블록 주소를 받는다. 주어진 블록 주소로 블록 끝에 있는 보호 바이트를 찾은 후 블록 크기를 읽는다. 블록 크기는 마지막 바이트 바로 앞 바이트에 들어 있다. 크기를 얻은 후에는 객체를 자유 상태로 설정하고 (마지막 바이트를 1로 설정하고) 해당하는 자유 리스트를 찾는다. 객체는 삭제 단계 끝 부분에 자유 리스트로 들어가게 된다. 구현은 Listing 20 을 참조한다.


Listing 20. MemoryManager::free 정의
                    
void MemoryManager::free(void* object)
  {
  char* init = static_cast<char*>(object);

  while(1)
    {
    int count = 0;
    while(*init != static_cast<char>(0xde))  
          //this loop shall never iterate more than 
      {                 // MAX_BLOCK_SIZE times and hence is O(1)
      init++;
      if(count > MAX_BLOCK_SIZE)
        {
        printf ("runtime heap memory corruption near %d", object);
        exit(1);
        } 
      count++; 
      }
    if(*(++init) == static_cast<char>(0xad))  // we have hit the guard bytes
      break;  // from the outer while 
    }
  init++;
  int blockSize = static_cast<int>(*init);
  switch(blockSize)
    {
    case 24: Byte24PtrList.push_front(object); break;
    case 32: Byte32PtrList.push_front(object); break;
    case 40: Byte40PtrList.push_front(object); break;
    default: break;
    }
  init++;
  *init = 1; // set free/available byte   
  }   
				

자유 리스트 메모리 관리자 전체 코드는 다운로드 절에서 참조한다. 코드는 실제로 동작하는 자유 리스트 메모리 관리자 구현과 위에서 설명한 목록을 모두 포함한다.

장점과 단점

위에서 설계한 메모리 관리자에는 몇 가지 장점이 있다.

  • 크기가 다양한 메모리 블록을 깔끔하게 처리한다.
  • 설계가 유연하며, 프로그램 요구사항에 따라 자유 리스트 크기를 결정하도록 확장할 수 있다.
  • 보호 바이트를 사용하여 메타 정보를 저장한다. 객체마다 저장되는 메타 정보는 여러 모로 유용한데, 특히 프로그램 실행 도중 힙 메모리 손상을 감지할 수 있다.

위 설계는 성능이 높아진다는 장점이 있는 반면 메모리를 많이 사용한다는 단점도 있다.


Go to the previous page15 페이지 중 9 페이지Go to the next page
    IBM 소개 개인정보 보호정책 문의