[C++] Effective C++ : Chapter 8 new와 delete를 내 맘대로
목차
Chapter 8 new와 delete를 내 맘대로
항목49: new 처리자의 동작 원리를 제대로 이해하자
Q. new operator, delete operator를 클래스 내부 연산자로 오버로딩했습니다! main()의 결과로 옳은 답은 몇번일까요?
class Unseen
{
public:
Unseen()
{
cout << "Unseen()" << endl;
}
~Unseen()
{
cout << "~Unseen()" << endl;
}
static void* operator new(size_t size) // static이 없어도 static인 것처럼 동작
{
cout << "Unseen new!" << size << endl;
void* ptr = ::malloc(size);
return ptr;
}
static void operator delete(void* ptr) // static이 없어도 static인 것처럼 동작
{
cout << "Unseen delete!" << endl;
::free(ptr);
}
int32 _hp = 100;
int32 _mp = 10;
}
void* operator new[](size_t size)
{
cout << “new[]! “ << size << endl;
void* ptr = ::malloc(size);
return ptr;
}
void operator delete[](void* ptr)
{
cout << "delete[]!" << endl;
::free(ptr);
}
int main()
{
Unseen* unseen = new Unseen();
delete unseen;
}
1번.
Unseen()
Unseen new! 8
Unseen delete!
~Unseen()
2번.
Unseen new! 8
Unseen()
~Unseen()
Unseen delete!
3번.
Unseen new! 4
Unseen()
~Unseen()
Unseen delete!
답: 2번
해제를 할 때에도 마찬가지로, 먼저 메모리를 해제한 후 소멸자를 호출해준다매크로로 만들어주는 것도 방법인데, 더 편한 방법은 Allocator 클래스, Memory 클래스 등을 만들어서 할당 정책을 관리하는 방법이라고 합니다.
나쁜 방법은 아니지만 클래스마다 만들 때 넣어준다는 점이 귀찮고 아쉽습니다.
먼저 메모리를 할당한 다음에, 생성자를 호출해줍니다
Q. 아래와 같이 '믹스인 양식'을 사용해 '신기하게 반복되는 템플릿 패턴(curiously recurring remplate pattern: CRTP)'를 사용하면 좋은 점에 대해 말해보세요.
template<typname T>
class NewHandleerSupport{
public:
static new_handler set_new_handler(new_handler p) throw();
static void* operator new(size_t size) throw(bad_alloc);
private:
static new_handler currentHandler;
};
template<typename T>
new_handler
NewHandleerSupport<T>::set_new_handler(new_handler p) throw()
{
new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template<typename T>
void* NewHandlerSupport<T>::operator new(size_t size) throw(bad_alloc)
{
NewHandlerHolder h(set_new_handler(currentHandler));
return ::operator new(size);
}
template<typename T>
new_handler NewHandlerSupport<T>::currentHandler = 0;
항목 50: new 및 delete를 언제 바꿔야 좋은 소리를 들을지를 파악해 두자
Q. new로 생성한 메모리에 delete를 하는 것을 잊어버리면 어떻게 될까요?
답: 메모리가 누출됩니다.
Q. 생성한 메모리(= new한 메모리)를 두 번 이상 delete하면 무슨일이 벌어질까요?
답: 미정의 동작이 발생할 수 있습니다.
동일한 메모리를 다시 해제하려고 하면, 메모리 관리 시스템은 이미 해제된 메모리를 다시 해제하려는 시도를 이해할 수 없고, 이는 메모리 할당 테이블을 손상시키거나 프로그램이 잘못된 메모리 접근을 시도하도록 할 수 있습니다.
int* ptr = new int;
delete ptr;
delete ptr; // 두 번째 delete에서 오류 발생
위 코드에서 두 번째 delete가 실행될 때, 이미 해제된 메모리를 다시 해제하려고 시도하기 때문에 문제가 발생합니다. 이러한 문제를 방지하기 위해, 포인터를 delete한 후에는 해당 포인터를 nullptr로 설정하는 것이 일반적인 관례입니다.
[ 수정본 ]
int* ptr = new int;
delete ptr;
ptr = nullptr; // 포인터를 nullptr로 설정
delete ptr; // nullptr에 delete를 호출해도 안전함
포인터가 더 이상 유효한 메모리를 가리키지 않도록 하여 double free 오류를 방지할 수 있습니다.
Q. 사용자 정의 operator new를 사용하는 이유가 뭘까요?
답:
1. 잘못된 힙 사용을 탐지하기 위해 사용합니다.
2. 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해 사용합니다.
3. 할당 및 해제 속력을 높이기 위해 사용합니다.
4. 기존 메모리 관리자의 공간 오버헤드를 줄이기 위해 사용합니다.
5. 임의의 관계를 맺고있는 객체들을 한 군데에 나란히 모아 놓기 위해 사용합니다.
6. 그때그때 원하는 동작을 수행하도록 하기 위해 사용합니다.
사용자 정의 operator new를 사용하면, 요구된 크기보다 약간 더 메모리를 할당한 후에 사용자가 실제로 사용할 메모리의 앞과 뒤에 오버런/언더런 탐지용 바이트 패턴을 적어두도록 만들 수 있습니다. 또한 operator delete는 누군가가 이 경계표지에 손을 댔는지 안 댔는지 점검하도록 만듭니다. 만일 이 경계표지 부분에 원래와 다른 정보가 적혀 있다면 할당된 메모리 블록을 사용하는 도중에 오버런이나 언더런이 발생한 것이므로, operator delete는 이 사실을 로그로 기록함으로써 문제를 일으킨 포인터 값을 남겨 놓을 수 있습니다.
- 오버런(overrun): 할당된 메모리 블록의 끝을 넘어 뒤에 기록하는 것.
- 언더런(underrun): 할당된 메모리 블록의 시작을 넘어 앞에 기록하는 것.
항목 51: new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아두자
Q. 아래의 코드를 읽고 while(true)의 탈출 조건으로 틀린 것을 고르시오.
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
if (size == 0) { // 0 바이트 요청이 들어오면, 1 바이트 요구로 간주
size = 1;
}
while (true)
{
// size 바이트를 할당함
if (할당에 성공)
{
return (할당된 메모리에 대한 포인터);
}
// 할당에 실패했을 경우, 현재의 new 처리자 함수가
// 어느 것으로 설정되어 있는지 찾아낸다
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);
if (globalHandler) (*globalHandler)();
else throw std::bad_alloc();
}
}
1. 메모리 할당이 성공한 경우
2. new 처리자 함수가 가용 메모리를 늘려주는 경우
3. new 처리자 함수가 다른 new 처리자 함수를 설치하는 경우
4. bad_alloc 혹은 bad_alloc에서 파생된 타입의 예외를 던지는 경우
5. size 바이트가 변경된 경우
답: 5번. size 값은 루프의 조건이나 탈출과 직접적인 연관이 없습니다.
※ 참고) C++에는 모든 독립 구조의 객체는 반드시 크기가 0이 넘어야 한다.
항목 39 참고
2024.05.07 - [⭐ Programming/Effective C++] - [C++] Effective C++ : Chapter 6 상속, 그리고 객체 지향 설계
항목 52:
Q. 아래 코드를 읽고 야기될 수 있는 문제점에 대해 설명해보세요.
// 전역 operator new의 표준 형태
void* operator new(std::size_t size) throw(std::bad_alloc); // 기본
void* operator new(std::size_t size, void* ptr) throw(); // 위치지정
void* operator new(std::size_t size, const std::nothrow_t& nt) throw(); // 예외불가
class Base{
public:
static void* operator new(std::size_t size, std::ostream& logStream) throw();
...
};
class Derived: public Base{
public:
static void* operator new(std::size_t size) throw();
...
};
int main()
{
Base *pb = new Base;
Base *pb = new (std::cerr) Base;
Derived *pd = new (std::clog) Derived;
Derived *pd = new Derived;
}
Base 클래스 내의 operator new는 기존의 표준 operator new 를 가립니다.
Derived 클래스 내에서 기본형 new 를 클래스 전용으로 다시 선언
int main()
{
Base *pb = new Base; // 에러. 전역의 operator new가 가려져 있음.
Base *pb = new (std::cerr) Base; // Base 클래스의 위치지정 new를 호출.
Derived *pd = new (std::clog) Derived; // 에러. Base 클래스의 위치지정 new가 가려져 있음.
Derived *pd = new Derived; // 정상.
}
'⭐ Programming > Effective C++' 카테고리의 다른 글
[C++] Effective C++ : Chapter 7 템플릿과 일반화 프로그래밍 (0) | 2024.05.27 |
---|---|
[C++] Effective C++ : Chapter 6 상속, 그리고 객체 지향 설계 (0) | 2024.05.07 |
[C++] Effective C++ : Chapter 5 구현 (0) | 2024.04.28 |
[C++] Effective C++ : Chapter 4 설계 및 선언 (0) | 2024.04.17 |
[C++] Effective C++ : Chapter 3 자원관리 (0) | 2024.04.13 |
댓글
이 글 공유하기
다른 글
-
[C++] Effective C++ : Chapter 7 템플릿과 일반화 프로그래밍
[C++] Effective C++ : Chapter 7 템플릿과 일반화 프로그래밍
2024.05.27 -
[C++] Effective C++ : Chapter 6 상속, 그리고 객체 지향 설계
[C++] Effective C++ : Chapter 6 상속, 그리고 객체 지향 설계
2024.05.07 -
[C++] Effective C++ : Chapter 5 구현
[C++] Effective C++ : Chapter 5 구현
2024.04.28 -
[C++] Effective C++ : Chapter 4 설계 및 선언
[C++] Effective C++ : Chapter 4 설계 및 선언
2024.04.17