여기서는 new 및 delete 키워드를 통해 C++ 프로그램에서 메모리 공간을 동적으로 할당받고 해제하는 방법에 대해서 짚어보겠습니다. 보통 변수나 객체를 소스 코드 내에서 선언하게 되면, 선언한 시점부터 함수나 코드블록 등이 끝날 때 까지 메모리 공간을 점유하게 됩니다. 메모리를 절약해야 하는 상황이라면, 동적 메모리 할당을 통해 한 개 또는 여러개의 변수나 객채를 위한 메모리 공간을 필요할 때만 할당받아 사용하는 것이 좋은 방법입니다.
1개의 변수나 객체를 생성하기 위해서는, 포인터 변수를 선언하고 new 키워드 뒤에 자료형이나 클래스의 이름을 붙여주면 되겠습니다. 할당받았던 메모리 공간을 해제하기 위해서는 delete 키워드를 사용하면 되는데요. delete 키워드 뒤에 할당받았던 공간을 가리키는 포인터 변수를 붙여주면 됩니다. 할당받았던 공간이 해제되는 과정에서 변수나 객체 역시 사라지게 되죠.
포인터는 메모리 상에서의 주소를 저장하기 위한 변수로서, 포인터가 생소하게 느껴지시는 분들은 시작하기에 앞서서 다음 포스팅을 읽어보시면 큰 도움이 되리라 생각합니다.
다음 예시는 1개의 정수형 변수를 생성하고, 그 값을 10으로 정의한 뒤에 메모리 공간을 해제하는 내용을 담고 있는 코드입니다.
/* 할당받은 메모리 공간의
* 변수를 가리키는 포인터 */
int *ptr_i;
/* 1개의 정수형 변수를 위한
* 메모리 공간 할당 */
ptr_i = new int;
// 변수의 값을 정의
(*ptr_i) = 10;
// ... 변수의 값 사용 ...
/* 동적으로 할당받았던
* 메모리 공간을 해제 */
delete ptr_i;
new int를 통해서 1개의 정수형 변수를 위한 공간을 할당받고, 이와 동시에 정수형 변수 1개가 생성이 됩니다. 이 변수 자체는 이름을 가지고 있지 않지만, 그 주소를 가리키는 포인터 변수 ptr_i를 통해서 접근 및 제어가 가능합니다.
실무적으로는 C언어의 malloc 함수처럼, 동일한 자료형을 가진 여러개의 변수 혹은 객체를 위한 공간을 할당받아서 사용하는것이 일반적입니다. 이 때는 할당받고자 하는 변수나 객체의 갯수를 대괄호 ([ ])안에 넣고 자료형이나 클래스 이름 뒤에 붙여주도록 합시다.
이렇게 할당받은 공간에 저장된 변수나 객체에 접근하기 위해서는 대괄호 안에 인덱스의 값을 넣고 메모리 주소를 가리키는 포인터 변수 이름 뒤에 붙여 주면 되겠습니다. 여기서 인덱스는 0 부터 [배열의 크기]-1 까지의 값을 가질 수 있습니다. 그리고 더 이상 필요가 없어져서 할당받은 공간을 해제하고자 하는 경우에는 delete 키워드 뒤에 대괄호를 붙여야 합니다.
다음 예시는 동적 메모리 할당을 통해 크기가 10인 정수형 배열을 생성하고, 저장된 변수들의 값을 인덱스와 동일하게 정의하는 코드입니다.
/* 동적으로 할당받고자 하는
* 배열의 크기 */
int size_i = 10;
/* 동적으로 할당받은 배열의
* 주소를 가리키는 포인터 */
int *array_i;
// 동적 메모리 할당
array_i =
new int [size_i];
// 배열의 값 정의
for (int i = 0; i < size_i; i++) {
array_i[i] = i;
}
// ... 배열의 값 사용 ...
// 할당받았던 메모리 공간을 해제
delete [] array_i;
1차원 배열 뿐만 아니라, 다중 포인터를 통해서 행렬이나 텐서를 위한 공간을 할당받는 것도 가능합니다. 예를 들어서 2중 포인터를 이용해서 행렬을 만들고 싶다면, 먼저 각 행 (row)을 가리키기 위한 포인터들의 배열을 할당받은 다음, 다시 각 행을 가리키는 포인터를 통해서 행렬의 값을 저장하기 위한 메모리 공간을 할당받으면 되겠습니다.
다음 예시는 크기가 5인 단위 행렬을 위한 공간을 할당받아, 그 값을 정의하는 코드입니다.
// 정사각행렬의 크기
int size_m = 5;
/* 행렬의 값을 저장하기 위한
* 2중 포인터 */
double **mtx_identity;
/* 첫번째 단계 :
* 행 (row)을 위한 동적 메모리 할당
* mtx_identity는 포인터의 베열을 가리킴. */
mtx_identity = new double *[size_m];
// 행 (row) 루프
for (int ir = 0; ir < size_m; ir++) {
/* 두번째 단게 :
* 열 (column)을 위한 동적 메모리 할당
* mtx_identity[ir]은 각 행의 배열을
* 가리킴. */
mtx_identity[ir] =
new double [size_m];
/* ir 행 - ic 열에서의
* mtx_identity[ir][ic]의 값 정의 */
for (int ic = 0; ic < size_m; ic++) {
if (ir == ic) {
mtx_identity[ir][ic] = 1.;
} else {
mtx_identity[ir][ic] = 0.;
}
}
}
// ... 할당받은 변수 사용 ...
// 동적으로 할당받은 메모리 해제
for (int ir = 0; ir < size_m; ir++) {
/* 먼저 각 행에 대해 할당받은
* 메모리 공간을 해제 */
delete [] mtx_identity[ir];
}
/* 그 다음 행들을 가리키는 포인터들의
* 배열을 해제 */
delete [] mtx_identity;
클래스 내부에서도 new, delete 키워드를 이용해서 동적으로 메모리를 관리할 수 있는데요. 예를 들어서 클래스를 초기화하는 함수에서 동적으로 메모리를 할당하고, 객체를 삭제할 때 호출되는 소멸자에서 할당받았던 메모리 공간을 해제하는 방식으로 코드를 구성할 수 있습니다.
class MyClass {
private :
// 배열의 크기
int size_i_;
/* 동적으로 할당받은 배열을
* 가리키는 포인터 */
int *array_i_;
/* 객체가 초기화되어
* 동적 메모리 할당이 이루어졌는지
* 알려주는 제어 변수 */
bool initialized_;
public :
// 생성자
MyClass() {
size_i_ = 0;
array_i_ = NULL;
initialized_ = false;
}
// 소멸자
~MyClass() {
free_array();
}
/* 객체를 초기화하고
* 동적으로 메모리를 할당받기
* 위한 함수 */
void init(int size_in) {
free_array();
size_i_ = size_in;
if (size_i_ < 1) {
return;
}
array_i_ =
new int [size_i_];
for (int i = 0; i < size_i_; i++) {
array_i_[i] = i;
}
initialized_ = true;
}
/* 동적으로 할당받았던 메모리를
* 해제하기 위한 함수 */
void free_array() {
if (!initialized_) {
return;
}
delete [] array_i_;
size_i_ = 0;
array_i_ = NULL;
initialized_ = false;
}
};
위의 예시에 등장하는 MyClass라는 클래스의 init이라는 멤버 함수는 동적으로 할당되는 배열의 크기를 인자로 받고 있습니다. 메모리를 할당받고 나면, 변수 initialized_의 값을 true로 정의하는데요. 이 제어변수가 필요한 이유는 소멸자에서 호출되는 free_array 함수에서 메모리 공간을 해제할지의 여부를 결정해야 하기 때문입니다. 메모리 공간을 해제하고 나면 initialized_의 값을 false로 바꿔서, 같은 주소를 가진 메모리 공간이 이중으로 해제되는 것을 방지합니다.
new, delete 키워드들을 통해 C++ 프로그램에서 메모리를 동적으로 관리하는 방식은 C언어의 malloc 및 free 함수와 유사한데요. malloc 함수를 통해 변수들을 저장하기 위한 메모리 공간을 할당받고, free 함수를 통해 해제하기 때문입니다. 중요한 차이점 중의 하나는, new 키워드와는 달리 malloc 함수는 클래스로부터 객체 배열을 할당받는데는 사용할 수 없다는 것입니다. C언어에서 malloc, free 함수들을 가지고 메모리를 동적으로 관리하는 방법에 대해서는 다음 포스팅을 참고하면 좋습니다.
같이 알고 있으면 좋은 C/C++ 팁들
함수를 인자로 사용하기
C언어 라이브러리 제작
OpenMP를 이용한 병렬 프로그래밍