C++ 동적 메모리 관리: 스택 vs 힙, new/delete 연산자
C++에서 동적 메모리가 실제로 어떻게 작동하는지 잘 이해하는 것은 좋은 C++ 프로그래머가 되기 위해 필수적입니다. C++ 프로그램의 메모리는 두 부분으로 나뉩니다.
스택(the stack) − 함수 내부에서 선언된 모든 변수는 스택에서 메모리를 차지합니다.
힙(the heap) − 프로그램의 미사용 메모리로, 프로그램 실행 시 동적으로 메모리를 할당하는 데 사용할 수 있습니다.
많은 경우, 특정 정보를 정의된 변수에 저장하는 데 얼마나 많은 메모리가 필요할지 미리 알지 못하며, 필요한 메모리 크기는 런타임에 결정될 수 있습니다.
주어진 타입의 변수를 위해 힙에서 런타임에 메모리를 할당할 수 있으며, 할당된 공간의 주소를 반환하는 C++의 특수 연산자를 사용합니다. 이 연산자를 new 연산자라 부릅니다.
동적으로 할당된 메모리가 더 이상 필요하지 않으면, delete 연산자를 사용해 new 연산자가 이전에 할당했던 메모리를 해제할 수 있습니다.
new와 delete 연산자
임의의 데이터 타입에 대해 동적으로 메모리를 할당하는 new 연산자 사용의 일반적인 구문:
new data-type;
여기서 data-type은 배열을 포함한 모든 내장 데이터 타입이나 클래스, 구조체 등 사용자 정의 데이터 타입이 될 수 있습니다. 내장 데이터 타입부터 시작해 보겠습니다. 예를 들어 double 타입 포인터를 정의하고 실행 시 메모리 할당을 요청할 수 있습니다. new 연산자로 다음 문장들을 사용해 이를 수행할 수 있습니다.
double* pvalue = NULL; // NULL로 초기화된 포인터
pvalue = new double; // 변수용 메모리 요청
자유 저장소가 소진되었다면 메모리 할당이 성공하지 못할 수 있습니다. 따라서 new 연산자가 NULL 포인터를 반환하는지 확인하고 적절한 조치를 취하는 것이 좋은 관행입니다.
double* pvalue = NULL;
if(!(pvalue = new double)) {
cout << "Error: out of memory." << endl;
exit(1);
}
C의 malloc() 함수는 여전히 C++에 존재하지만, malloc() 사용은 피하는 것이 권장됩니다. new가 malloc()보다 나은 주된 장점은 new가 단순히 메모리를 할당하는 것이 아니라 객체를 생성한다는 점으로, 이는 C++의 주된 목적입니다.
동적으로 할당된 변수가 더 이상 필요하지 않다고 느낄 때, delete 연산자로 해당 변수가 자유 저장소에서 차지하는 메모리를 해제할 수 있습니다.
delete pvalue; // pvalue가 가리키는 메모리 해제
위 개념들을 모아 'new'와 'delete' 작동 방식을 보여주는 예제:
Live Demo
#include <iostream>
using namespace std;
int main() {
double* pvalue = NULL;
pvalue = new double;
*pvalue = 29494.99; // 할당된 주소에 값 저장
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue; // 메모리 해제
return 0;
}
위 코드를 컴파일하고 실행하면:
Value of pvalue : 29495
배열을 위한 동적 메모리 할당
문자 배열, 즉 20자 문자열을 위한 메모리를 할당하고 싶다고 가정해 보세요. 위에서 사용한 같은 구문으로 동적으로 메모리 할당:
char* pvalue = NULL;
pvalue = new char[20]; // 변수용 메모리 요청
방금 생성한 배열을 제거하려면:
delete[] pvalue; // pvalue가 가리키는 배열 삭제
new 연산자의 같은 일반적인 구문을 따라 다차원 배열에도 할당할 수 있습니다.
double** pvalue = NULL;
pvalue = new double[3][4]; // 3x4 배열 메모리 할당
그러나 다차원 배열 메모리 해제 구문은 위와 동일하게 유지됩니다.
delete[] pvalue; // pvalue가 가리키는 배열 삭제
객체를 위한 동적 메모리 할당
객체는 단순 데이터 타입과 다를 바 없습니다. 예를 들어 객체 배열을 사용해 개념을 명확히 하는 다음 코드를 보세요.
Live Demo
#include <iostream>
using namespace std;
class Box {
public:
Box() { cout << "Constructor called!" << endl; }
~Box() { cout << "Destructor called!" << endl; }
};
int main() {
Box* myBoxArray = new Box[4];
delete[] myBoxArray; // 배열 삭제
return 0;
}
Box 객체 4개 배열을 할당하면 단순 생성자가 4번 호출되고, 마찬가지로 이 객체들을 삭제할 때도 소멸자가 같은 횟수만큼 호출됩니다.
위 코드를 컴파일하고 실행하면:
Constructor called!
Constructor called!
Constructor called!
Constructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!