본문 바로가기
Programming/C, C++

LV2 C++ 동적 할당(Dynamic Allocation)

by Dev_카페인 2022. 9. 8.
반응형

 

 

[lv2/C++] 동적 할당(Dynamic Allocation)

 

C와 C++에서는 포인터를 사용하여 메모리를 직접 관리할 수 있다. 앞에서 설명한 것과 같이 포인터는 메모리상의 주소를 가리키고 있는데 포인터를 이용하여 런타임(프로그램이 실행되는 동안)시 이름이 없는 메모리를 할당할 수 있다. C에서는 malloc()과 free()함수를 통하여 메모리를 할당하거나 해제할 수 있지만 C++에서는 new와 delete연산자를 통해 메모리를 관리할 수 있다. 물론 C++은 C를 기반으로 하기 때문에 malloc()과 같은 함수도 사용이 가능하지만 이번에는 new와 delete를 이용한 동적 메모리 할당과 해제를 할 것이다. 기본 형식은 다음과 같다.

typeName *pointerName = new typeName;
ex) int *ptr = new int;
ex) delete ptr;

new 키워드를 통해 할당된 메모리는 데이터형식에 맞는 공간을 Heap영역에 할당한 후 해당 주소를 리턴한다. 이 주소번지가 포인터 변수에 담기게 되고 *ptr을 통해 해당 주소에 접근할 수 있게 된다. new를 통해 할당한 메모리는 사용 후에 delete 연산자를 사용하여 반환을 해 주어야 한다. 그렇지 않을 경우 할당된 메모리가 삭제되지 않고 계속 남아있으면서 메모리 누수가 발생하게 된다. new 연산자를 통해 동적으로 할당된 배열을 생성할 수 있다 (*동적 배열과 동적으로 할당된 배열은 다르다.)

int *ptrArr = new int[5];
delete [] ptrArr;

동적으로 배열을 할당할 때도 new 키워드는 배열의 시작 주소를 리턴한다. 배열의 공간을 해제하는 방법은 delete와 포인터 변수명 사이에 배열 표시(대괄호[])를 추가해주면 된다. 이렇게 할당된 배열은 포인터 연산이 가능하여 포인터가 지시하는 데이터형의 바이트 수 만큼 증가하게 되어 다음 항목을 참조할 수 있다.

출처 : https://iyk2h.tistory.com/100

아래 프로그램은 new 키워드의 여러가지 초기화 방법이다.

#include <iostream>
using namespace std;

int main()
{
	char *cPtr = new char[20]{ "Hello World!" };
	cout << "cPtr : " << cPtr << endl;
	delete cPtr;

	int *iPtr = new int(10);
	cout << "*iPtr : " << *iPtr << endl;
	delete iPtr;

	float *fPtr = new float(3.14);
	cout << "*fPtr : " << *fPtr << endl;
	delete fPtr;

	int *iArrPtr = new int[5]{ };	// 0 으로 초기화
	cout << "iArrPtr[0] : " << iArrPtr[0] << endl;
	delete iArrPtr;

	return 0;
}

new 키워드를 사용해 메모리를 직접 관리하는 만큼 할당에 따른 문제점도 발생하기 마련인데 메모리상 할당할 공간이 부족하거나 할당하지 못할 경우의 에러나 할당이 해제된 메모리를 가리키는 포인터(댕글링포인터(Dangling Pointer))를 역참조 하여 임의로 조작하는 경우 정의되지 않은 동작을 초래한다.

 

메모리 할당 실패

new 키워드를 사용했을 때 할당에 실패하게 되면 C언어의 malloc() 과 다르게 bad_alloc라는 익셉션을 리턴하게 된다.

#include <iostream>
using namespace std;

int main()
{
	try {
		int *iPtr = new int[5];
	}
	catch (bad_alloc &badAlloc) {
		cerr << "Bad_Alloc이(가) 발생하였습니다. : " << badAlloc.what() << endl;
	}
    	delete iPtr;

	return 0;
}

할당에 실패했을 때 malloc() 처럼 널 포인터로 리턴받길 원한다면 new 키워드를 사용시 (nothrow)를 포함해주면 된다.

#include <iostream>
using namespace std;

int main()
{
	int *iPtr = new(nothrow) int;
	if(iPtr == nullptr)
		cout << "메모리 할당 실패\n";
	delete iPtr;

	return 0;
}

 

에러를 직접 야기시키지는 못했지만 꼭 알아두길 바란다. 

 

댕글링 포인터(dangling pointer)

C++에서는 할당되지 않은 메모리의 내용이나 삭제되는 포인터의 값에 대해서는 보장하지 않는다. 동적 할당을 받은 포인터를 사용하다가 메모리 할당을 해제했을 경우 포인터는 할당되지 않은 메모리를 가리키게 되는데 이 포인터를 댕글링 포인터(dangling pointer)라고 한다. 댕글링 포인터를 역참조 하게 되면 할당되지 않은 메모리에 임의로 접근할 수 있고 이는 정의되지 않은 동작을 초래한다. 만약 댕글링 포인터를 통해 메모리에 접근하여 임의로 조작하게 된다면 큰 피해를 입을 가능성이 생긴다. 

#include <iostream>
using namespace std;

int main()
{
	int *iPtr = new int;
	int *dang = iPtr;

	cout << "iPtr에 할당된 주소 : " << iPtr << endl;
	cout << "dang이 가리키는 주소 : " << dang << endl;

	delete iPtr;

	cout << "iPtr할당 해제 후 iPtr이 가리키는 주소 : " << iPtr << endl;
	cout << "iPtr할당 해제 후 dang이 가리키는 주소 : " << dang << endl;

	iPtr = nullptr;
	dang = 0;

	cout << "iPtr에 nullptr 설정 후 가리키는 주소 : " << iPtr << endl;
	cout << "dang에 nullptr 설정 후 가리키는 주소 : " << dang << endl;

	return 0;
}

댕글링 포인터가 생기지 않도록 하는 방법은 할당 해제 후 포인터가 가리키는 것을 0으로 설정하거나 nullptr값을넣어주는 것이다. delete 후 해당 포인터에 0이나 nullptr값을 넣는 습관을 들이도록 하자.

반응형