[C++] 동적 메모리 관리 new, delete와 댕글링 포인터
동적 메모리 관리
동적 메모리 관리(Dynamic Memory Management)는 프로그램 실행 중에 메모리를 동적으로 관리하기 위한 메커니즘입니다.
프로그램이 실행되기 전에 정적으로 결정되지 않는 데이터 크기나 구조를 처리하는 데 사용됩니다.
실행 중에 메모리 크기를 동적으로 결정하고 필요 없으면 반환할 수 있습니다.
동적 메모리 할당은 주로 힙 영역에서 이루어집니다.
C++에서는 new
와 delete
키워드를 통해 처리됩니다.
다음과 같은 정적 메모리 할당의 한계로 인해 동적 메모리 할당이 필요합니다.
char name[20];
long long array[1000000];
name
의 경우 배열의 크기만큼 실제로 사용되지 않으면 메모리가 낭비됩니다.
스택 영역에는 스택의 크기가 정해져 있습니다.
하지만 array
처럼 스택 영역의 크기를 초과하면 스택 오버플로우가 발생하며 프로그램이 종료되는 등의 문제가 발생합니다.
new
new
키워드는 힙 영역에서 동적 메모리를 할당하고, 필요한 크기 만큼의 메모리를 할당합니다.
만약 성공적으로 메모리가 할당된다면, 해당 메모리의 시작 주소를 반환합니다.
사용자 정의 클래스의 경우 메모리가 할당된 후 생성자가 호출됩니다.
사용하는 방법은 다음과 같습니다.
자료형* 변수명 = new 자료형;
자료형* 변수명 = new 자료형(초기값);
int* ptr1 = new int;
int* ptr2 = new int(3);
int* ptr3 = new int{75};
int* arr = new int[5];
이렇게 할당된 메모리에 포인터로 접근하게 됩니다.
접근하는 방법은 기존 포인터를 사용하는 방법과 동일합니다.
만약 할당에 실패할 경우 기본적으로 std::bad_alloc
예외를 던집니다.
할당에 실패하는 경우는 다음과 같습니다.
- 프로그램이 힙 메모리 접근 권한을 가지지 못할 경우
- 할당 요청 크기가 지나치게 클 경우
- 시스템의 전체 메모리가 부족할 경우
- 메모리 과다 사용을 방지하기 위해 운영 체제에서 할당 요청을 거부 했을 경우
- 스택 메모리가 손상되어 힙 영역의 메모리 할당에 영향을 줄 경우
- 힙 메모리가 부족한 경우
- 힙 메모리가 충분하더라도, 메모리가 조각화되어 필요한 크기의 연속된 메모리를 확보할 수 없을 경우
delete
delete
키워드는 new
키워드로 동적 할당된 메모리를 반환합니다.
이때 실제로 무엇을 삭제하는게 아닌 메모리를 다시 운영 체제로 반환할 뿐입니다.
클래스 객체의 경우 소멸자가 호출된 후 메모리가 반환됩니다.
이 delete
키워드를 사용하지 않으면 메모리 누수가 발생합니다.
호출한 후 댕글링 포인터를 방지하기 위해 ptr
에 대해서 nullptr
로 초기화 해주는 것이 좋습니다.
만약 두 번 호출할 경우 런타임 오류가 발생합니다.
nullptr
을 가리키는 ptr
에서 호출할 경우 아무런 동작을 하지 않기 때문에 런타임 오류가 발생하지 않습니다.
스택 메모리에 대해 delete
키워드를 사용하면 오류가 발생합니다.
사용하는 방법은 다음과 같습니다.
delete 포인터변수;
delete[] 배열포인터변수;
delete ptr;
delete[] arr;
댕글링 포인터
댕글링 포인터(Dangling Pointer)는 이미 할당 해제된 메모리를 가리키는 포인터를 의미합니다.
이런 포인터는 유효한 메모리를 가리키지 않지만, 할당 해제된 메모리 주소를 가지고 있습니다.
댕글링 포인터는 역참조하거나 삭제하면서 프로그램 크래시나 메모리 손상을 유발할 수 있습니다.
C++에서는 메모리를 직접 관리하므로 댕글링 포인터를 관리하는 것은 아주 중요한 문제입니다.
댕글링 포인터의 원인
메모리 할당 해제 후 해제된 주소를 가리키는 포인터를 사용하려 할 때 댕글링 포인터가 발생합니다.
int* ptr new int(10);
delete ptr;
std::cout << *ptr; //오류
delete ptr;
로 메모리가 해제된 후, ptr
은 유효한 메모리를 가리키지 않게됩니다.
하지만 ptr
을 사용하려고 시도하기 때문에 댕글링 포인터가 발생합니다.
스택에 존재하는 지역 변수의 주소를 반환하고, 그 주소를 사용하는 경우에도 댕글링 포인터가 발생할 수 있습니다.
지역 변수는 생명 주기가 끝나면 메모리가 자동으로 해제되기 때문에 해당 변수의 주소가 유효하지 않습니다.
int* func()
{
int a = 20;
return &a;
}
int main()
{
int* ptr = func();
std::cout << *ptr;
}
a
변수는 함수가 종료되면서 스택 영역에서 사라집니다.
ptr
은 유효하지 않은 메모리 주소를 가리키게 되며, 댕글링 포인터가 발생합니다.
댕글링 포인터를 방지하는 방법
메모리를 해제한 후, 해당 메모리의 주소를 가지고 있는 포인터를 nullptr
로 설정해주면 댕글링 포인터를 방지할 수 있습니다.
nullptr
은 어떤 유효한 주소도 가리키지 않으므로 잘못된 메모리 영역을 참조하는 것을 막을 수 있습니다.
int* ptr new int(10);
delete ptr;
ptr = nullptr;
스마트 포인터를 사용하여 동적 메모리 관리를 자동으로 처리할 수 있습니다.
delete
키워드를 수동으로 호출할 필요가 없으며, 댕글링 포인터를 방지할 수 있습니다.
스마트 포인터는 std::unique_ptr
, std::shared_ptr
, std::weak_ptr
가 있습니다.
#include <memory>
unique_ptr<int> ptr = make_unique<int>(10);
댓글남기기