항목 50 : new 및 delete를 언제 바꿔야 좋은 소리를 들을지를 파악해두자

  • operator new / delete를 바꾸고 싶은 이유
    1. 잘못된 힙 사용 탐지를 위해
      • delete를 잊으면 메모리 누수, 두번 이상 delete시 미정의 동작을 한다.
        • new가 할당된 메모리 주소 목록을 유지하고 delete에서 목록으로부터 제거하게 만들면 실수를 쉽게 잡을 수 있다.
        • 데이터 오버런(할당된 메모리 블록 끝을 넘어 뒤에 기록), 데이터 언더런(할당된 메모리 블록 시작을 넘어 앞에 기록) 발생 시 메모리를 약간 더 할당해서 오버런/언더런 탐지용 바이트 패턴을 구현할 수 있다.
    2. 효율을 향상시키기 위해
      • 컴파일러가 기본적으로 제공하는 new / delete 함수는 지극히 대중적이고 온건지향 스타일의 전략을 취한다. 즉 개발자가 자신의 프로그램이 동적 메모리를 어떻게 사용하는지 알고 있으면 만들어 쓰는 편이 우수한 성능을 낼 확률이 높다.
    3. 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해
    4. 할당 및 해제 속력을 높이기 위해
      • 기본 제공 범용 할당자는 사용자 정의 버전보다 꽤 느린 경우가 적지 않다.
    5. 기본 메모리 관리자의 공간 오버헤드를 줄이기 위해
      • 범용 메모리 관리자는 사용자 정의 버전보다 느리고 메모리도 많이 잡아먹는 사례가 허다하다.
    6. 적당히 타협한 기본 할당자의 바이트 정렬 동작을 보장하기 위해
      • 컴파일러 중 기본적으로 제공되는 new 함수가 double에 대한 동적 할당 시 8바이트 정렬을 보장하지 않는 게 있다. -> 보장하게 수정 시 프로그램 수행 성능 향상이 가능하다.
    7. 임의의 관계를 맺고 있는 객체들을 한군데에 나란히 모아놓기 위해
      • 페이지 부재 발생 횟수를 최소화 하기 위해 가능한 한 적은 페이지를 차지하도록 하면 효과를 볼 수 있다. 이러한 메모리 군집화는 위치지정 new 및 위치 지정 delete로 쉽게 구현이 가능하다.
    8. 그때 그때 원하는 동작을 수행하도록 하기 위해
      • ex) 메모리 할당 해제를 공유 메모리에 하고 싶을 때 공유 메모리 조작이 C API로밖에 할 수 없을 때
      • ex) 응용 프로그램 데이터의 보안 강화를 위해 해제한 메모리 블럭에 0을 덮어쓰는 사용자 정의 operator delete를 만드는 경우
  • 직접 만들 때 직면하는 문제 : 바이트 정렬
  • 바이트 정렬 : 아키텍처적으로 특정 타입 데이터가 특정 종류 메모리 주소를 시작주소로 하여 저장될 것을 요구사항으로 두고 있다.
    • ex) 포인터 : 4의 배수에 해당하는 주소에 맞춰 저장
  • 일반적으로는 꼭 만들어 쓸 이유가 없으면 만들 필요는 없다.
  • 부스트 풀 라이브러리 : 크기가 작은 객체를 많이 할당할 경우에 맞춰 최적화 되어 있다.

항목 51 : new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아두자

  • operator new 구현 시 요구사항
    1. 반환값이 제대로일 것
    2. 가용 메모리가 부족할 시 new 처리자 함수를 호출할 것
    3. 크기가 없는(0바이트) 메모리 요청에 대한 대비책을 갖춰야 한다.
    4. 실수로 ‘기본’ 형태의 new가 가려지지 않도록 한다.
  • 반환값 문제 : new는 메모리 할당 실패마다 new 처리자 함수를 호출하는 식으로 메모리 할당을 2회 이상 시도한다.
    • 메모리 해제하는 데 실마리가 되는 동작을 new 처리자에서 할 것으로 가정하고 있다.
    • 즉 new가 예외를 던지는 경우 = new 처리자 함수에 대한 포인터가 NULL
  • 0바이트가 요구되었을 때조차 operator new 함수는 적법한 포인터를 반환해야 한다.

  • operator new의 무한루프를 빠져나오는 조건
    1. 메모리 할당이 성공한다
    2. new 처리자 함수가 처리해준다.
      • new 처리자가 올바른 동작을 보장해야 하는 이유 : 루프가 끝나지 않기 때문이다.
  • operator new 멤버 함수는 상속이 되는 함수
    • 특정 클래스를 위해 준비된 operator new일 확률이 높다.
      • 자식 클래스에서 사용하면 안된다.
    • 틀린 메모리 크기가 들어왔을 때를 시작부에서 확인한 후 표준 operator new를 호출하게 수정한다.
  • 배열에 대한 메모리 할당 : operator new
  • operator new[] 안에서 해줄 일은 원시 메모리 덩어리 할당 뿐
    • 배열 메모리에 아직 생기지도 않은 클래스 객체에 대해 아무것도 할 수 없다.
      • 배열 안에 몇 개 객체가 들어갈지 계산조차 안된다.
        1. 객체 하나가 얼마나 클지 확정할 방법이 없다.
          • Base::operator new[]에서 할당한 배열 메모리에 들어가는 객체수를(요구된 바이트 수 / sizeof(Base))로 계산할 수 없다.
          • 파생 클래스는 대체적으로 Base보다 크다 : 파생 클래스 객체 배열 할당 시 기본 클래스 배열 new가 호출될 수 있다.
        2. operator new[]에 넘어가는 size_t 타입 인자는 객체들을 담기에 딱 맞는 메모리양보다 더 많게 설정되어 있을 수 있다.(16)
  • operator delete 구현 시 요구사항
    • C++는 널 포인터에 대한 delete 적용이 항상 안전하도록 보장한다는 사실을 잊지 말고 보장할 것
  • 가상 소멸자가 없는 기본클래스의 파생클래스 객체를 삭제하려 할 경우 operator delete로 C++가 넘기는 size_t 값이 엉터리일 수 있다.