Programming

C++ 이야기 세번째: new 와 delete

steloflute 2014. 5. 3. 23:49

http://yesarang.tistory.com/39

 

[이글의 최신 Update 문서는 항상 여기에서 확인할 수 있습니다]

C++ 이야기 세번째입니다. 개발자가 아니신 분들이나 C++ 로 주로 개발하지 않으시는 분들은 별로 관심이 가는 내용이 아닐 것 같네요.

크~ 벌써 세번째에 접어 들었네요. 이번에는 new 와 delete에 대해서 알아보도록 하겠습니다. new 와 delete에 대해서는 다 잘 아는 건데 이것보다 딴 걸 알고 싶으신 분은 잠깐 기다려 보세요. 이 글을 다 읽으신 후에는 new 와 delete의 오묘한 세계에 다시 한 번 놀라실 겁니다. 저도 놀랐다니까요~ 글쎄

먼저 여러분이 가장 기본적으로 알고 있는 것부터 확인하고 넘어갈까요 ?

* new: 객체 하나를 동적으로 할당해 줍니다.
* delete: 동적으로 할당된 객체 하나를 삭제합니다.
* new []: 객체의 배열을 동적으로 할당해 줍니다.
* delete []: 동적으로 할당된 객체의 배열을 삭제합니다.

다 잘 알고 있는 내용일 것입니다. 그렇지만 약간 틀리기도 했습니다. 조금 더 정확히 쓰면 다음과 같이 표현할 수 있습니다.

* new: 객체 하나를 동적으로 할당하고, 초기화시켜 줍니다.
* delete: 객체 하나를 소멸시키고, 삭제합니다.
* new []: 객체의 배열을 동적으로 할당하고, 하나 하나를 초기화시켜 줍니다.
* delete []: 객체의 배열 하나 하나의 요소를 소멸시키고, 배열을 삭제합니다.

다시 말하면 new/new[]는 할당(allocation) + 초기화(initialization) 가 합쳐져 있고, delete/delete [] 는 소멸(destruction) + 삭제(release) 가 합쳐져 있습니다.(실은 new/new [] 에서는 좀 더 복잡한 처리가 이루어 집니다) 컴파일러가 new/new[] 또는 delete/delete[]를 만나면 자동적으로 위와 같이 작동하도록 코드를 생성해 준다는 것입니다.

이게 바로 개선된 C 로서의 C++의 특성입니다. C 로 프로그램을 작성할 경우, 할당 후, 초기화를 하지 않아서 또는 반대로 소멸과정을 거치지 않은 채로 삭제해서 발생하는 문제가 있었는데, 이 에러의 가능성을 근본적으로 줄인 C++의 특성인 거죠.

그럼 여기서 질문 하나 하고 들어가죠. 왜 C에는 malloc() 과 free() 로 모든 메모리 할당/해제를 처리했는데, C++에서는 new/delete, new []/delete [] 와 같이 객체 하나와 객체 배열을 할당/해제하는 연산자를 따로 두었을까요 ?

만약 new []/delete []가 없다면 delete 만으로 어떻게 delete [] 의 동작 방식을 구현할 수 있을까요 ? 제가 우선 생각나는 것은 new 로 할당할 때, 할당되는 메모리에 객체에 대한 서술자를 위한 메모리를 추가적으로 할당하고, 거기에 메모리에 대한 정보를 서술해 두면 될 것 같네요. 이 서술자가 있으면 그걸 보고 객체 배열 하나 하나의 요소를 소멸시킬 수 있을 겁니다.

+------------+------------+
|     n      | object x n |
+------------+------------+

n = 1 이면 single object, >1 이면 array

물론 꼭 이렇게 할 필요는 없겠지만, 대강 할당되는 메모리마다 서술자가 필요할 거라는 느낌은 드실 겁니다. 그런데 조금만 생각해 보면 굳이 객체 하나를 생성하는데 n 이라는 overhead 가 필요할까 라는 생각이 드실 겁니다. 우리 내면 깊숙이 잠재해 있는 공학도의 근성이 저걸 없애 버리고 싶은 충동을 느끼게 하지 않나요 ? 그럼 어떻게 ? 그렇죠~ new/delete, new[]/delete[]를 구분하는 거죠.

그래서, new 와 new [] 가 할당하는 메모리 배치는 약간 차이가 있을 것입니다. 그 차이는 new []는 최소한 배열의 개수를 표현하는 서술자를 추가적으로 더 할당하게 된다는 거니다. 다음과 같이요

        +------------+
new:    |   object   |
        +------------+
             +------------+------------+
new [] :     |     n      | object x n |
             +------------+------------+

컴파일러마다 구현할 수 있는 방식은 다를 수 있긴 하겠죠. So what ? 그래서 뭐가 어쩌냐구요 ? 음... 그거야 어찌 됐든 버그 없는 세상에서 행복하게 살자는 얘기죠. 그래서 여기 오늘의 중요한 본론 들어갑니다.

"new 로 할당했으면 delete 로 해제, new [] 로 할당했으면 delete []로 해제하시라"

그겁니다. 그럼 다음 코드를 실행시키면 어떤일이 일어날까요 ?

int main()
{
  int* pi1 = new int[10];
  int* pi2 = new int(10);
  // Oops!! delete []는 서술자를 보고 여러번 destruction을 수행
  delete [] pi2;
  delete pi1;                // Oops!! destruction이 한 번만 수행
  return -1;
}

말 그대로 undefined behavior 입니다.  어떻게 될지 알 수 없다는 거죠. 여러분에게 운이 따르면, 제대로 수행되는 것이고, 운이 없으면 잠자고 있는 새벽에 여러분에게 전화가 오는 거죠.

new/delete, new []/delete [] 쌍을 맞추실래요 아니면 새벽에 회사 나가서 디버깅 하실래요 ? 그냥 항상 신경 써서 맞추십시오.

이제 다음 얘기로 넘어가 보겠습니다.

(헉헉 다음 얘기는 나중에... 힘들다...)