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

Jangmanbo·2023년 4월 1일
0

Effective C++

목록 보기
16/33
std::string* stringArray = new std::string[100];
...

delete stringArray;

언뜻 보면 문제 없어 보이는 코드이다. new로 메모리를 할당하고 delete로 해제했기 때문이다.

그러나 이 코드는 미정의 동작으로 빠져버린다.
설령 동작하더라도 stringArray가 가리키는 100개의 string 객체들 중 99개의 객체들은 소멸하지 못할 것이다.


new 표현식 사용
1. 메모리가 할당
2. 할당된 메모리에 대해 한 개 이상의 생성자 호출

delete 표현식 사용
1. 기존에 할당된 메모리에 대해 한 개 이상의 소멸자 호출
2. 메모리 해제


메모리 구조

단일 객체의 메모리 배치 구조와 객체 배열의 메모리 배치 구조는 다르다.
특히, 배열을 위해 만들어지는 힙 메모리에는 대개 배열원소의 개수가 존재한다. 이는 delete 연산자가 적용되는 객체의 개수이며 즉 소멸자가 호출되는 개수이다.

그렇다면 delete를 적용할 때 delete 연산자에게 배열에 크기 정보가 있다는 것을 알려줄 책임은 사용자에게 있다. 때문에 delete가 아닌 delete[]를 사용해야 한다. []를 붙이지 않으면 객체 배열이 아닌 단일 객체라고 인식하게 된다.

std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];

delete stringPtr1;	// 단일 객체 삭제
delete [] StringPtr2;	// 객체 배열 삭제

이렇게 단일 객체인 경우 delete를, 객체 배열인 경우 delete[]를 통해 메모리를 해제해야 한다.


If...

delete [] stringPtr1;
delete stringPtr2;

단일 객체 stringPtr1delete[]를 사용한다면??

stringPtr1 앞쪽의 메모리 몇 바이트를 읽고 이를 배열 크기라고 해석한다.
이후 배열 크기의 개수만큼 소멸자를 호출할 것이고 소멸자를 호출하고 있는 메모리는 배열에 속해 있지도, 그 메모리가 자신이 소멸시키려는 타입의 객체도 아닐 것이다.

객체 배열 stringPtr2delete를 사용한다면??

소멸자가 호출되어야 하는 횟수보다 더 적게(1번) 호출될 것이다.


부록

new와 delete의 형태를 맞춰야 한다는 규칙은 typedef을 사용할 때 특히 주의해야 한다.

typedef std::string AddressLines[4];

std::string *pal = new AddressLines;	// new 연산자를 사용한 것으로 보이지만 실제로는 new string[4]
delete [] pal;	// new AddressLines만 보고 delete 연산자를 사용하면 안된다.

이렇게 메모리를 할당한 코드만 보고서는 delete를 쓸 지, delete[]를 쓸 지 알 수 없으므로 배열 타입은 typedef으로 만들지 않는 것이 좋다.

참고로 해결법은 간단하다. 배열 말고 vector<string>으로 정의하면 된다.



정리
각 표현식에 대응되는 표현식을 쓰자!

  • new => delete
  • new[] => delete[]

0개의 댓글