댕글링 포인터(Dangling Pointer)에 대해서
#C-Family댕글링 포인터와 C++에서의 예방법
댕글링 포인터란?
댕글링 포인터(Dangling Pointer)는 이미 해제되었거나 더 이상 유효하지 않은 메모리를 가리키고 있는 포인터를 말한다.
즉, 포인터 변수는 그대로 남아 있는데, 포인터가 가리키는 메모리 공간이 해제된 상태이다.
댕글링 포인터의 존재가 왜 위험이 되는가?
- 해제된 메모리를 다시 사용하면 예측 불가능한 동작을 일으킬 수 있다.
- 프로그램이 동작 중에
Segmentation Fault가 날 수 있다.
댕글링 포인터가 생기는 상황
int* p = new int(10);
delete p; // 메모리 해제
*p = 20; // ※ p는 현재 댕글링 포인터 위 코드 문법 오류는 발생하지 않지만 실행 도중 오류가 발생한다. 이미 해제된 공간에 값을 넣으려고 하니 메모리 오류가 발생하는 것이다.
같은 메모리 영역을 두 변수가 같이 가리키고 있는 상황에서 변수 하나를 해제할 경우도 댕글링 포인터가 될 위험이 있다.
int* p1 = new int(10);
int* p2 = p1;
delete p1;
*p2 = 20; // p2가 가리키는 영역은 해제 되었음 -> 댕글링 포인터 댕글링 포인터가 발생하는 이유는 개발자가 잘못된 위치에서 메모리 해제(free, delete)를 해서 발생 한다고 생각한다. 이러한 위험을 예방하기 위해서 C++11 부터는 Smart Pointers를 지원한다.
Smart Pointers
스마트 포인터는 <memory> 헤더 파일의 std 네임스페이스 안에 정의되어 있다.
스마트 포인터는 메모리 해제를 개발자가 직접 하지 않아도 알아서 변수 소멸 시점에 해제 해준다.
MSDN에 소개된 예제를 보면은 다음과 같다.
void UseRawPointer()
{
// Using a raw pointer -- not recommended.
Song* pSong = new Song(L"Nothing on You", L"Bruno Mars");
// Use pSong...
// Don't forget to delete!
delete pSong;
}
void UseSmartPointer()
{
// Declare a smart pointer on stack and pass it the raw pointer.
unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));
// Use song2...
wstring s = song2->duration_;
//...
} // song2 is deleted automatically here.모던 C++ 에서 지원하는 스마트 포인터는 세 가지가 있다.
unique_ptrshared_ptrweak_ptr
각각의 특성은 다음과 같다.
unique_ptr
기본 포인터의 소유자를 하나만 허용한다.
int main()
{
unique_ptr<int> p1(new int(10));
cout << "p1이 가리키는 값 : " << *p1 << endl;
return 0;
}위 코드의 실행하면 출력이 잘 된다. p1이 가리키는 메모리 영역을 p1이 혼자 차지하고 있는 경우 unique_ptr의 사용은 문제가 되지 않는다.
unique_ptr<int> p1(new int(10));
cout << "p1이 가리키는 값 : " << *p1 << endl;
auto p2 = p1;
cout << "p2가 가리키는 값 : " << *p2 << endl;
return 0;위 코드는 문법 오류가 발생한다. 하나의 메모리 영역을 변수 p1, p2 두 개가 함께 소유하려고 하기 때문이다. 이럴 경우는 shared_ptr을 아래와 같이 사용한다.
shared_ptr
int main()
{
shared_ptr<int> p1(new int(10));
cout << "p1이 가리키는 값 : " << *p1 << endl;
cout << "참조 개수(count) : " << p1.use_count() << endl;
auto p2 = p1;
cout << "p2가 가리키는 값 : " << *p2 << endl;
cout << "참조 개수(count) : " << p1.use_count() << endl;
shared_ptr<int> p3(p2);
cout << "p3가 가리키는 값 : " << *p3 << endl;
cout << "참조 개수(count) : " << p1.use_count() << endl;
return 0;
}하나의 메모리 공간에 대해 여러 변수가 같이 소유할 수 있다. use_count() 함수로 해당 메모리에 참조하는 포인터가 몇 개인지 확인할 수 있다. 변수들의 수명이 끝나 카운트가 0이 되면 자동으로 delete 해준다.
weak_ptr
weak_ptr은 단독으로 사용될 수 없으며, shared_ptr과 함께 쓰일 수 있으며, shared_ptr에서 발생할 수 있는 문제점을 해결하기 위해서 사용된다.
shared_ptr에서 서로가 서로를 가리키는 shared_ptr 포인터를 가리킨다면 reference count가 0이 되지 못하여 메모리가 해제되는 않는 순환 참조(circular reference)가 발생할 수 있다.
이러한 경우 count 값은 증가 안 하도록 weak_ptr를 쓸 수 있다.