개요
댕글링 포인터 .. 이름 참 역동적이라 까먹진 못 할 것 같다. C++에서 포인터를 활용하여 구현을 하다보면 동적할당에 대한 개념을 접할 것이다. 동적할당을 통해 값을 참조하고, 그 값을 다른 포인터에서도 참조하는 구조라고 가정해보자. 둘 중 하나의 포인터를 delete한 상태에서 다른 포인터를 통해 참조하는 데이터에 접근을 해보면 -249218232 머시기 이상한 값을 얻게 될 것이다. 이런 상황을 Dangling Pointer라고 지칭하고, 이런 댕글링 포인터의 문제는 Smart Pointer로 해결할 수있다고 한다. 어떻게 해결할 수 있는 것인지 한 번 알아보자.
1. Dangling Pointer
#include <iostream>
using namespace std;
void func5() {
int* ptr = new int(40); // 힙 메모리에 정수 40 할당
int* ptr2 = ptr;
cout << "ptr adress = " << ptr << endl;
cout << "ptr2 adress = " << ptr2 << endl;
cout << *ptr << endl;
delete ptr;
cout << *ptr2 << endl;
}
int main() {
func5();
return 0;
}
ptr과 ptr2가 40이라는 값을 가진 동일한 메모리 영역을 참조하고 있다. delete를 통해 ptr이 참조하고 있는 메모리를 해제해주었지만, ptr2가 접근을 하는 상황이다. ptr2뿐만 아니라 ptr도 여전히 같은 메모리를 가리키고 있기때문에 ptr을 통해 접근을 해도 문제가 발생한다. 이런 상황을 Dangling Pointer라고 지칭한다.
댕글링 포인터를 악용하여 해제된 메모리 영역에 의도적으로 악성 데이터를 주입하는 상황이 벌어질 수도 있고,
해제된 메모리 영역에 새로운 데이터가 할당이 되었을 경우 잘못된 데이터에 접근하는 문제가 발생할 수도 있다.
때문에 우리는 Dangling Pointer를 방지하는 방법으로 Smart Pointer를 지향해야한다.
2. Smart Pointer
Smart Pointer가 무엇인지 알아보기전에 Reference Counter라는 개념을 선행해야한다.
Reference Counter
![]() |
Reference Counter는 메모리를 관리하는 기법중 하나로, 객체나 리소스를 참조하는 참조의 개수를 추적하는 방식이다. 위의 그림을 보면, 하나의 메모리 영역을 두 개의 포인터가 참조하고 있는 것을 알 수 있다. 때문에 처음엔 레퍼런스 카운터가 2이고, 하나의 포인터가 참조를 초기화한 경우 레퍼런스 카운터는 1이 된다. 남은 하나의 포인터도 참조를 초기화할 경우 레퍼런스 카운터는 0이 되고, 이때 자동으로 메모리가 해제된다.
C++에서 레퍼런스 카운터를 활용해 구현하는 방법은 2가지이다. unique_ptr과 shared_ptr로 자세한 정의는 아래에서 알아보자.
unique_ptr
레퍼런스 카운터가 최대 1인 스마트 포인터이다. 참조하고 있는 메모리 영역에 대해 소유권을 다른 포인터에게 넘기는 것은 가능하나, 동시에 2개 이상 소유할 수 없다.
#include <iostream>
#include <memory> // unique_ptr 사용
using namespace std;
int main() {
// unique_ptr 생성
unique_ptr<int> ptr1 = make_unique<int>(10);
// unique_ptr이 관리하는 값 출력
cout << "ptr1의 값: " << *ptr1 << endl;
// unique_ptr은 복사가 불가능
// unique_ptr<int> ptr2 = ptr1; // 컴파일 에러 발생!
// 소유권 이동 (move 사용)
unique_ptr<int> ptr2 = move(ptr1);
if (!ptr1) {
cout << "ptr1은 이제 비어 있습니다." << endl;
}
cout << "ptr2의 값: " << *ptr2 << endl;
// 범위를 벗어나면 메모리 자동 해제
return 0;
}
shared_ptr
레퍼런스 카운터가 N개가 될 수 있는 스마트 포인터이다. 현재 레퍼런스 카운터를 가져오는 use_count( )와 현재 포인터를 초기화하는 reset( ) 함수를 사용할 수 있다.
#include <iostream>
#include <memory> // shared_ptr 사용
using namespace std;
int main() {
// shared_ptr 생성
shared_ptr<int> ptr1 = make_shared<int>(10);
// ptr1의 참조 카운트 출력
cout << "ptr1의 참조 카운트: " << ptr1.use_count() << endl; // 출력: 1
// ptr2가 ptr1과 리소스를 공유
shared_ptr<int> ptr2 = ptr1;
cout << "ptr2 생성 후 참조 카운트: " << ptr1.use_count() << endl; // 출력: 2
// ptr2가 범위를 벗어나면 참조 카운트 감소
ptr2.reset();
cout << "ptr2 해제 후 참조 카운트: " << ptr1.use_count() << endl; // 출력: 1
// 범위를 벗어나면 ptr1도 자동 해제
return 0;
}
느낀 점
나는 Dangling Pointer 상황이 포인터가 2개 이상일 때만 발생한다고 알고 있었다. 그러나 포인터가 1개인 상황에서도 메모리를 해제하면, 해당 포인터가 여전히 같은 메모리를 가리키고 있어 Dangling Pointer 상황이 발생할 수 있다는 사실을 알게 되었다.
이럴 때마다 느끼는 것은, 잘못된 지식을 가지고 있으면서도 그것을 모르는 것이 가장 위험하다는 점이다. 앞으로 학습할 때는 항상 경각심을 가지고 올바른 태도로 배우도록 해야겠다.
'C++ > 문법 정리' 카테고리의 다른 글
[C++] 객체지향 프로그래밍의 개념 (1) | 2025.01.02 |
---|---|
[C++] Template의 사용법 (0) | 2024.12.30 |
[C++] 오버로딩 vs 오버라이딩 (1) | 2024.12.27 |
[C++] Class의 개념 (0) | 2024.12.27 |
[C++] 참조와 포인터의 차이 (1) | 2024.12.23 |