본문 바로가기

C++/Effective C++

항목 16: new 및 delete 를 사용할 때는 형태를 반드시 맞추자

delete 사용시 주의 사항

아래 코드를 보고 문제점을 찾아보자.

string *stringArray = new string[100];

delete stringArray;

그렇다.
new 는 배열을 할당했는데, delete 는 그렇지 않다.
stringArray 가 가리키는 100 개의 string 객체중 99개는 정상적인 소멸과정을 거치지 못할 가능성이 크다.

new 와 delete 연산자의 동작

new 연산자를 사용해 어떤 객체를 동적 할당하게 되면, 두 가지의 내부 동작이 이뤄진다.

1) 메모리가 할당된다. (operator new 라는 이름의 함수가 쓰인다. 항목 49. 51 참조)
2) 할당된 메모리에 대해 한 개 이상의 생성자가 호출된다.

delete 연산자를 쓸 때는 다음과 같은 두 가지 내부 동작이 이뤄진다.

1) 기존에 할당된 메모리에 대해 한개 이상의 소멸자가 호출된다.
2) 그 후에 메모리가 해제된다. (operator delete 라는 이름의 함수가 쓰인다.)

그렇다면 delete 연산자가 적용되는 객체는 몇개 일까?
바로 소멸자가 호출되는 횟수 만큼 delete 연산자가 적용된다.

배열의 delete

삭제되는 포인터는 객체 하나만 가리킬까, 아니면 객체의 배열을 가리킬까?

new 로 힙에 할당된 단일 객체의 메모리 배치구조는 객체 배열에 대한 메모리 배치 구조와 다르다.
일단, 배열을 위해 만들어지는 힙 메모리에는 배열 원소의 개수가 들어가는 것이 큰 차이다.
이 때문에 delete 연산자는 소멸자가 몇 번 호출될지를 쉽게 알 수 있다.
반면, 단일 객체용 힙 메모리에는 이런 정보가 없다.

배치 구조가 다르다는 것은 아래와 같다는 뜻이다.

한개의 객체 Object
객체의 배열 n(index 개수) | Object | Object | ...

대다수의 경우 컴파일러는 저렇게 구현한다.

어떤 포인터에 대해 delete 를 적용할 때, delete 연산자한테 '배열 크기 정보가 있다' 라는 것을 알려주는 것은
개발자 한테 달려 있다.
이 정보를 대괄호 쌍 [] 을 delete 뒤에 붙여줌으로 써 알려 줄 수 있다.
그렇지 않으면 그냥 단일 객체라고 간주한다.

string *stringArray1 = new string;
string *stringArray2 = new string[100];

delete stringArray1; //객체 한개를 삭제한다.
delete[] stringArray2; //객체의 배열을 삭제한다.

typedef 주의 사항

아래와 같이 배열을 typedef 를 지정해놓을 경우, 어떤 형태의 delete 를 적어줘야 하는지는
typedef 의 작성자가 책임을 져야 한다.

typedef string AddressLines[4];

string *pa1 = new AddressLines; // new string[4] 와 같은 뜻이다.

//delete 의 형식 역시 [] 를 포함해야 한다.
delete [] pa1;

웬만하면 배열 타입을 typedef 로 만들지 않는 것이 좋다.
c++ 의 string 이나 vector 를 사용하면 동적 할당 배열이 필요해지는 경우가 거의 없기 때문에,
이는 어려운 일은 아니다.

위의 예제에서는 AddressLines 를 vector<string> 타입으로 만들면 된다.

요약

  • new 표현식에 [] 를 썼으면 delete 에도 [] 를 쓰자. 반대의 경우도 마찬가지