KoreanFoodie's Study

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

Tutorials/C++ : Advanced

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

GoldGiver 2022. 10. 25. 16:34

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

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

핵심 :

개발자가 스스로 사용자 정의 new 및 delete 를 작성하는 데는 여러가지 이유가 있다. 여기에는 수행 성능 향상, 힙 사용 시의 에러 디버깅, 힙 사용 정보 수집 등의 목적이 포함된다.

 

operator new 와 operator delete 를 바꾸는 가장 흔한 세 가지 이유를 한 번 보자.

  • 잘못된 힙 사용을 탐지하기 위해 : 데이터 오버런(overrun) 및 언더런(underrun) 을 탐지하기 위해 탐지용 바이트를 추가로 할당할 수 있다.
  • 효율을 향상시키기 위해 : 힙 단편화 등 실행환경에 맞게 커스터마이징
  • 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해 : 로그 수집 등

 

경계 표지 패턴의 간단한 구현을 한 번 보자.

static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;

void*operator new(std::size_t size) throw(std::bad_alloc)
{
	using namespace std;
	// 경계 표지 2개를 앞뒤에 붙일 수 있을 만큼 메모리를 확장
	size_t realSize = size + 2 * sizeof(int);

	void *pMem = malloc(realSize);
	if (!pMem) throw bad_alloc();

	// 메모리 블록의 시작 및 끝부분에 경계 표지를 기록
	*(static_cast<int*>(pMem)) = signature;
	*(reinterpret_cast<int*>(static_cast<Byte*>(pMem) + realSize - sizeof(int))) = signature;

	// 앞쪽 경계 표지 바로 다음의 메모리를 가리키는 포인터를 반환
	return static_cast<int*>(pMem) + sizeof(int);
}

위 구현은 operator new 에 쓰이는 관례(new 처리자 호출 루프 구현)이 지켜지지 않았지만, 자세한 건 항목 51에서 다루기로 한다.

사실 위의 문제는, 바이트 정렬(alignment) 의 문제를 갖고 있다. 여기서는 경계 표지를 4바이트(int 의 사이즈) 로 설정해서 포인터를 경계 표지 시작지점 + 4 인 곳을 리턴한다. 하지만 해당 operator new 를 사용하는 녀석이 double 을 할당하고, 각각의 주소값을 8바이트로 잡는 경우, 문제가 생길 수 있다.

이외에도 추가적인 문제가 생길 수 있지만, 사용자 operator new 와 operator delete 를 만들 때는, 다음과 같은 이유로 만드는 것인지를 잘 고려해 보자.

  • 할당 및 해제 속력을 높이기 위해
  • 기본 메모리 관리자의 공간 오버헤드를 줄이기 위해
  • 적당히 타협한 기본 할당자의 바이트 정렬 동작을 보장하기 위해 : double 시에는 8바이트 정렬을 보장해야 프로그램 성능이 좋다
  • 임의의 관계를 맺고 있는 객체들을 한 군데에 나란히 모아 놓기 위해
  • 그때그때 원하는 동작을 수행하기 위해
Comments