Effective C++/Chapter 3: 자원관리

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

yeoul0714 2025. 5. 2. 01:06

1. 개요


std::string *stringArray = new std::string[100];

delete stringArray;

 
이 코드는 얼핏 보기에는 아무런 문제가 없어 보입니다.
 
메모리를 동적할당받고 잘 삭제해주고 있으니까요
 
그러나 이것은 완벽히 잘못된 코드입니다.
 
왜냐하면 동적할당으로 string객체를 100개만큼 생성해주고 있기 때문입니다.
 
위의 코드 처럼 한다면 1개의 객체 받게 해제시키지 못합니다.


 

2. new, delete의 동작방식


new를 실행하면 2가지 동작이 수행됩니다.
 
1. 메모리가 할당됩니다.
2. 할당된 메모리에 대해 한 개 이상의 생성자가 호출됩니다. ([]도 포함해서 그렇습니다.)
 
delete도 비슷합니다.
 
1. 할당된 메모리에 대해 한개 이상의 소멸자가 호출됩니다.
2. 메모리가 해제됩니다. (operate delete함수 사용)
 
그렇다면 delete 연산자가 적용되는 객체의 수는 소멸자가 호출 하는 횟수와 동일합니다.
 
쉽게 말해서 delete와 소멸자 호출은 하나의 쌍이라는 것이죠


 

3. 동적 할당과 메모리 구조


https://yeoul0714.tistory.com/10

 

[면접] [CS] 메모리 구조와 Heap(힙), Stack(스택) 메모리 차이

컴퓨터에서 프로그램이 실행되기 위해서는 OS가 프로그램을 메모리에 Load하는 과정이 필요합니다. 메모리는 어떻게 세분화 되어 구분되어 있는지 알아보도록 하겠습니다. 기본적으로 다음과

yeoul0714.tistory.com

 
new 연산자가 실행되면 메모리에는 어떤일이 일어날까요?
 

int* ptr = new int;

int* ptr2 = new int[10];

 
ptr, ptr2 모두 heap저장된 데이터에 대한 메모리 주소를 가지고 있습니다.
 
예전에 들었던 의문은 사실상 포인터 주소는 배열이든 단일 객체든간에 가장 처음 요소의 주소만 들고있는데
 
만일 배열이라면 어떻게 그 크기를 알고 컴파일을 할까하는 작은 의문이 있었습니다.
 
사실 저 두 코드는 heap에 들어가는 메모리의 구조가 완전히 다릅니다.
 
배열의 경우에는 heap 메모리에 배열원소의 개수가 함께 저장됩니다.
 
컴파일러가 해당 부분을 읽어서 적절히 컴파일했던 것입니다.
 
그러므로 delete는 소멸자가 몇번 호출될지 알 수 있습니다.
 
delete[] 작동 원리: delete[] ptr2;가 호출되면:

  • 런타임 시스템은 ptr2에서 약간 앞쪽으로 이동하여 저장된 배열 크기를 읽습니다.
    (추가 정보는 보통 우리가 쓰는 배열 앞 메모리에 저장됩니다.)
  • 이 크기 정보를 바탕으로 배열의 각 요소에 대해 역순으로 소멸자를 호출합니다.
    (동적 할당될때에 0번 인덱스 객체부터 생성됐음으로 그 역순으로 소멸자가 호출된다는 의미입니다.)
  • 전체 메모리 블록(메타데이터 + 배열 요소)을 해제합니다.
    (메타데이터는 배열 크기에 대한 정보입니다.)


 

4. 컴파일러에게 크기를 알려주는 것은 우리다.


어떤 포인터에 대해서 delete를 쓸때 배열 크기 정보가 있다는 것을 알려주는 것은 프로그래머의 역할입니다.
 
알려주는 방식은 바로 delete에 []를 붙히는 것입니다.
 
그러면 delete가 포인터가 배열을 가리키고 있다는 것을 알게 되고 모든 객체를 적절히 해제할 수 있습니다.
 
만약 붙혀주지 않는다면 단일 객체로 간주하게 됩니다.
 
만약에 단일 객체에 대해서 삭제를 할때 []를 사용하면 어떻게 될까요?
 
앞쪽 몇바이트를 읽고 그것이 배열의 크기라고 간주하고 우리가 상상하기 싫은 삭제를 감행할 것입니다.
 
이러한 개념이전에 적절한 delete를 쓰는 것은 매우 간단합니다.
 
그냥 new를 사용할 때 []을 썼다면 여기에 대응되는 delete 표현식 에도 []을 쓰는 것입니다. 
 
만약 []을 쓰지 않다면 delete에도 []을 쓰지 않으면 됩니다.


 
 

5. typedef를 사용할 때도 신경쓰자


typedef로 정의 된 어떠한 타입의 객체를 메모리에 생성하려고 new를 쓰면 나중에 어떤 형태의 delete를 적어줘야 하는가에 대한
 
고민이 있을 수 있습니다.
 
그렇다면 한가지 예시를 보도록 합시다.

typedef std::string AddressLines[4];

 
이 코드에 대해서 new르 사용한다면
 

std::string *pal = new AddressLines;

new AddressLines는 string*을 반환하게 됩니다.
 
사실 이것은 new string[4]와 같기 때문입니다.
 
해제할 때도 아주 잘해주어야 합니다.
 

delete pal; // 이렇게 하면 안됩니다.

delete [] pal; // 이렇게 해야 합니다.

 
그런데 사실 typedef로 무엇을 만들기 보다는
 
vector와 string같은 좋은 클래스 탬플릿이 있기에 잘 활용하면 배열이 필요한 경우가 거의 없습니다.
 

이것만은 잊지 말자!

  • new 표현식에 []를 썼으면, 대응되는 delete 표현식에도 []을 써야 합니다. 마찬가지로 
    new 표현식에 []를 안 썼으면, 대응되는 delete 표현식에도 []를 쓰지 말아야 합니다.

 

https://yeoul0714.tistory.com/54

 

[Effective C++] 항목 17: new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로

1. 개요이번 장의 내용은 컴파일러마다 실행순서가 달라져서 발생하는 문제를 막는 방법에 대해 소개하는 항목입니다. 예를 들어보도록 하겠습니다.int Priority();void ProcessWidget(std::tr1::shared_ptr pw,

yeoul0714.tistory.com