버퍼 오버런 및 언더런을 탐지하기 쉬운 형태로 만들어 주는 전역 operator new를 후다닥 만들어 봤다.
static const int SIGNATURE = 0xDEADBEEF; typedef unsigned char BYTE; void* operator new(std::size_t size) throw(std:bad_alloc) { using namespace std; size_t realSize = size + 2 * sizeof(int); // 경계표시 2개 추가 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<BYTE*>(pMem) + sizeof(int); // 경계표시 바로 다음 메모리를 반환 }
위 Code는 좀 까다로운 문제들을 무시했는데 그중 하나가 바이트 정렬(alignment) 이다. 많은 컴퓨터의 경우 아티켁처적으로 특정 타입의 데이터가 특정 종류의 메모리 주소를 시작 주소로 하여 저장될 것을 요구하는 경우가 많다. 예를 들어 포인터는 4의 배수에 해당되는 주소에 맞추어 저장되어야 하며 double은 8의 배수에 해당하는 주소에 맞추어 저장되어야 한다는 것이다. 이걸 지키지 않은 경우 프로그램이 다운 될 수도 있고, 정말 다행스러울 경우 실행속도가 엄청나게 느려지는 것으로 마무리 될수도 있다.
new/delete 를 직접 구현하는 것이 생각만큼 쉽지는 않다. 꼭 만들어 쓸 경우가 없다면 굳이 들이댈 필요는 없다. 농담이 아니라 진짜 필요가 없다. 메모리 관리 함수만 전문적으로 다루는 상업용 제품들도 많이 있다.링크 한 번 다시 하는 것만으로 개선된 기능과 향상된 성능을 맛 볼 수 있다는 거다. [물론 실탄(돈)이 있어야 겠지만]
그럼 언제 new/delete를 다른 것으로 대체하는 작업을 해야 의미가 있겠는가 ?
- 잘못된 합 사용을 탐지하기 위해 : 버퍼 오버런, 언더런 (할당된 메모리 블록 앞이나 뒤를 넘어서 기록하는 것) 탐지
- 동적 할당 메모리의 실제 사용에 관한 Log를 수집하기 위해
- 할당 및 해제 속력을 높이기 위해 : 부스트의 Pool 라이브러리 같이 고정된 크기의 객체만 만들어주는 등...
- 기본 메모리 관리자의 공간 오버헤드를 줄이기 위해
- 적당히 타협한 기본 할당자의 바이트 정렬 동작을 보장하기 위해 : 바이트 정렬을 보장해주지 않는 Compiler 사용시
- 임의의 관계를 맺고 있는 객체들을 한 군데에 나란히 모아 놓기 위해 : 페이지 부제(page fault) 발생 횟수 최소화
- 그때그때 원하는 동작을 수행하도록 하기 위해 : 보안 강화를 위해 delete한 메모리를 0으로 덮어쓰기 등...
* 개발자가 스스로 사용자 정의 new 및 delete 를 작성하는 데는 여러 가지 나름대로 타당한 이유가 있습니다. 여기에는 수행 성능을 향상시키려는 목적, 힙 사용 시의 에러를 디버깅하려는 목적, 힙 사용 정보를 수집하려는 목적 등이 포함됩니다.
댓글 없음:
댓글 쓰기