- new 표현식에 []를 썼으면, 대응되는 delete 표현식에도 []를 써야 한다.
마찬가지로 new 표현식에 []를 안 썼으면, delete 표현식에도 []를 쓰면 안 된다.
new, delete 사용 시 주의점
std::string *stringArray = new std::string[100];
...
delete stringArray;
문제가 없어 보이는 코드이지만, 미정의 동작을 보이게 된다.
최선의 경우에도 stringArray가 가리키는 100개의 string 객체 중 99개는 정상적으로 소멸되지 않을 가능성이 크다.
삭제되는 포인터가 객체 하나만 가리키는지 객체의 배열 가리키는지에 따라 delete가 소멸자를 몇 번 호출할지 결정한다.
단일 객체와 객체 배열에 대한 메모리 배치구조가 다르기 때문이다.
특히, 배열을 위해 만들어지는 힙 메모리는 배열원소의 개수가 포함되어 있다.
이 정보를 가지고 delete 연산자는 소멸자가 몇 번 호출될지 알 수 있다.
즉, 어떤 포인터에 대해 delete를 적용할 때, delete 연산자에게 배열 크기 정보를 넘겨주는 역할은 사용자가 해야 된다는 얘기이다. 대괄호 쌍을 delete 뒤에 붙여 주면 delete가 배열에 대해 적용이 된다.
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[100];
...
delete stringPtr1; // 객체 한 개 삭제
delete[] stringPtr2; // 객체 배열 삭제
만약 stringPtr1에 delete[]를 적용한다면 앞쪽의 메모리 몇 바이트를 읽고 배열의 크기라고 해석할 것이다.
그다음 배열의 크기만큼 소멸자를 호출하기 시작하면 자신이 관리하는 메모리를 넘어서 소멸시킬 수 있다는 뜻이다.
stringPtr2에 []를 사용하지 않으면 소멸자 호출 횟수가 적어 미정의 동작이 일어날 수 있다.
정리하자면 new 연산에 []를 썼으면 delete도 []를, new 연산에 []를 쓰지 않았으면 delete도 []를 쓰지 말아야 한다.
동적 할당된 메모리에 대한 포인터를 멤버 데이터로 갖고 있는 클래스를 만드는 중이며 이 클래스에서 제공하는 생성자도 여러 개일 경우에 특히 이 규칙을 지켜야 한다. 포인터 멤버를 초기화하는 부분인 생성자에서 new 형태를 똑같이 맞출 수밖에 없기 때문이다. 이렇게 하지 않으면 소멸자에서 어떤 형태의 delete를 써야 할지 알 수 없다.
그리고 typedef를 사용할 때는 특히 주의해야 한다.
typedef std::string AddressLines[4]; // 객체 배열
std::string *pal = new AddressLines; // new string[4]
delete[] pal;
typedef에서 객체 배열을 사용하면 까먹지 말고 delete에서 []를 사용해야 하는데 직관적이지 않기 때문에 사용하지 않는 것이 좋다.