사실 저게 왜 다른지를 자세히 설명하자면 ABI 등등해서도 알아둬야할게 좀 있긴 한데 최대한 간단하게 설명하자면
일단 new 표현이랑 operator new()에 대해서 알아둘 필요가 있어.
myclass *ptr = new myclass(); 같은 new expression은 내부적으로 다음과 같이 처리돼.
myclass *ptr = ::operator new(sizeof(myclass)); // 메모리 할당
new (ptr) myclass(); // 생성자 호출
new[]의 경우에는 조금 다른데, myclass *arr = new myclass[n];는 다음처럼 처리돼.
myclass *arr = ::operator new(n * sizeof(myclass)); // 메모리 할당
for(size_t i = 0; i < n; ++i) {
new (arr + i) myclass(); // 생성자 호출
}
delete ptr도 비슷하게 표현하자면
if(ptr) {
ptr->~myclass(); // 소멸자 호출
::operator delete(ptr);
}
여기까지는 그닥 어렵지 않은데, 문제는 delete[]야.
new expression같은 경우에는 객체가 몇개인지가 인자 n으로 넘어오는데, delete[] 같은 경우에는 몇개의 객체를 해제해야하는지 알 수가 없어.
그래서 ABI마다 다르긴 하지만 보통 new[]를 할때, 몇개의 객체를 갖고 있는지에 대한 정보를 담는 cookie를 추가해서 메모리를 할당하곤 해. (http://mentorembedded.github.io/cxx-abi/abi.html#array-cookies 참고)
이렇게 처리하면 myclass *arr = new myclass[n]; 은 다음과 같은 일들을 수행하고
void *ptr = ::operator new(sizeof(size_t) + n * sizeof(myclass)); // 메모리 할당
*static_cast<size_t*>(ptr) = n;
myclass *arr = reinterpet_cast<myclass*>((size_t*)ptr + 1);
for(size_t i = 0; i < n; ++i) {
new (arr + i) myclass(); // 생성자 호출
}
delete[] arr은 다음과 같은 일들을 수행해
size_t *ptr = reinterpret_cast<size_t*>(arr) - 1;
for(size_t i = 0; i < *ptr; ++i) {
(arr + i)->~myclass(); // 소멸자 호출
}
::operator delete(ptr);
실제로 저 코드대로 똑같이 수행된다는 건 아니고, 대충 저런 과정을 거친다는거야.
진짜로 저런 식으로 되는지 간단하게 확인해보려면 ::operator new랑 ::operator delete을 재정의해보면 확인할 수 있어.
myclass의 크기가 2이고 3개를 할당하니까 6byte만 할당해야하는데 10바이트가 할당되는걸 볼 수가 있을거야. 객체의 개수를 담을 size_t 만큼의 공간을 더 할당하는거지.
좀 더 추가하자면, array cookie를 붙이는건 모든 객체에 대해 소멸자를 호출해주기 위한 거라서, 소멸자가 trivial할 경우에는 array cookie를 안붙이는게 보통이야. 위에 참고로 링크한 문서에도 'No cookie is required if the array element type T has a trivial destructor' 라고 되어있어.
작동과정을 설명하기 위해서 간단하게 코드로 표현했지만 ABI마다 다를 수 있는 내용이니까 왜 delete와 delete[]를 구분해서 사용해야하는지만 이해하면 될 거야.
요약. new/delete, new[]/delete[] 쌍은 무조건 맞춰줘야한다. delete [] arr+1 이 된다는건 개소리.
존나 정석 설명이다. operator new 연관해서 설명해주려고 했는데 나도 확실히 몰랐는데 님덕분에 다시알게 됨 ㄳ
근데 한가지 의문스러운게 소스중에 void *ptr = ::operator new(sizeof(size_t) + n * sizeof(myclass)); // 메모리 할당*static_cast<size_t*>(ptr) = n; myclass *arr = reinterpet_cast<myclass*>((size_t*)ptr + 1); for(size_t i = 0; i < n; ++i) { new (arr + i) myclass(); // 생성자 호출 }
이렇게되어있는데 이거 좀이상한거갖은데 size_t하나에 인자로받은 myclass 사이즈개수에 n개만큼 사이즈를잡아서 0번째 위치에 size_t로 캐스팅해서 사이즈를 저장한다 그거데 어째서 생성자 호출에는 첫번째 인덱스가 아니라 0번째인덱스부터 도는거냐? 어쨋건 size_t를 위한 메모리 할당이니까 캐스팅후에 myclass를 호출하면 안되지않음?
저말대로라면 classA* ptr = new classA[15]; 이런식으로 만들때 생성자가 15개가아니라 16개만들어지는거아니냐
메모리를 할당할 때 || size_t | [0] | [1] | [2] || 같은 레이아웃으로 할당하는거야.
---------------- ^ptr -- ^array
그래 그건알겠는데 왜 size_t부분에 생성자를 호출하는거야
음?? size_t 부분에 생성자 호출 안하는데. myclass *arr = reinterpet_cast<myclass*>((size_t*)ptr + 1); 요기에 +1 해주잖오
아 ㅈㅅ 내가잘못봤네.. 니가맞음
ㅈㅅㅈㅅ reinterpet_cast 를재대로안봄 소스 잘짰네 니가직접짰어?
엉 근데 실제로 저런 코드보다는 쪼오끔 더 복잡한 구조로 돌아가긴 하는데 이해를 위한거니까 뭐 이 정도면 충분할 듯?
ㅋㅋ 잘짰다 한줄한줄 생각좀많이한듯
재밌네요