게임학원, 게임프로그래머 취업 전문 교육기관 DirectX11/12 자체엔진 게임개발과정,서버프로그래밍,자료구조,알고리즘,유니티,언리얼 게임학원, 언리얼학원 C++ 댕글링포인터 스마트포인터 제작기 1화
본문 바로가기

C++ 댕글링포인터 스마트포인터 제작기 1화

댕글링포인터(이미 삭제되거나 사라진 메모리를 가리키는 포인터)에 대해서 알아보도록 하겠습니다. 댕글링포인터에 사실 댕글링 포인터에 대해서 사람들이 이해하고 있는 것은 대부분 맞습니다. 이 글은 그보다는 좀더 깊게 들어가서 아마 몰랐을 부분에 대해서 이야기하고 싶습니다.

댕글링포인터가 생기는 이유는 다음과 같습니다.

1.     메모리를 해제했지만 해제한 메모리에 접근했다.

2.     함수호출시 만들어진 지역변수의 메모리를 지속적으로 사용하려고 했다.

대표적으로 다음과 같은 코드가 있습니다.

 

자 다음의 코드는 실제 어디에서 에러가 날까요? 사람들이 착각하는 것은 Print에서부터 에러가 날 거라고 생각하지만 실제로는 두번째 Ptr->Print2()에서 에러가 납니다. 이것이 첫번째로 사람들이 오해하는 점입니다.

기본자료형에 대한 동적할당은 그 값을 사용하는 것 자체가 기본자료형의 상태를 변화시키는 일이기 때문에 곧바로 에러가 나지만 클래스는 사용자 정의 자료형으로서 그에 대한 맴버함수 내부에서 this를 사용하는 연산을 했을 때만 에러가 나게 됩니다. 그것을 표현하기 위해서 맴버함수에 주석으로 this란 마치 보이지는 않지만 맴버함수에서 받는 인자 값처럼 표현한 것입니다.

이에 대해서 설명한 이유는 몇몇 학생들은 클래스포인터의 댕글링화에 대해서 인지하지 못할 때 저와 같이 this를 사용하지 않은 함수를 사용하는 경우가 있기 때문입니다.

그렇다면 이런 클래스 포인터의 댕글링포인터화를 막기위해서 유용한 방법 중 하나가 스마트 포인터입니다. 스마트포인터는 기존 TR1라이브러리에서 시험되고 C++에서부터 표준으로 지정된 std::shared_ptr<T> 를 사용하거나 직접 만들어서 사용하는 방법이 있습니다.

그럼 일단 기본적인 std::shared_ptr<T>의 사용법부터 보도록 하겠습니다.

굉장히 간단합니다. std::shared_ptr<T>는 템플릿 클래스이며 그 안에 동적할당된 데이터의 포인터를 넣어주면 자동으로 레퍼런스 카운트 방식으로 통해서 관리해 줍니다. 레퍼런스 카운트 방식이란 다음과 같습니다.

 

 

원본 데이터는 최초에 std::shared_ptr<T> 에 들어가게 되면 그를 담당하는 레퍼런스 카운트 변수가 1증가하게 됩니다. 그 상태에서 다른 std::shared_ptr<T> 에 관리될 때마다 1씩 증가하여 만약 3개의 std::shared_ptr<T> 에 원본데이터를 보관했다면 레퍼런스 카운트는 3이 될 것입니다. 이 그리고 이것은 use_count()라는 함수를 통해서 확인할 수 있습니다.

이와 같은 경우에 std::shared_ptr<T> 의 소멸자에서는 각 레퍼런스 카운트를 -시키기 때문에 최종적으로는 마지막 std::shared_ptr<T> 이 소멸될 때 레퍼런스 카운트는 0가 되면서 관리하고 있던 포인터를 스마트포인터가 자동으로 delete하는 방식으로 동작합니다.

그렇기 때문에 기본자료형의 관리는 쉽게 이루어지지만 내가 직접 만드는 클래스 같은 경우에는 만약 소멸자가 private:으로 구현되어 있을 경우에는 std::shared_ptr<T>스마트 포인터가 그 데이터를 관리할 수가 없습니다.

이런 std::shared_ptr<T> 몇가지 주의할 사항이 존재하는데 다음과 같이 사용하면 문제가 생길 수 있습니다.

이런 경우 각 std::shared_ptr<T> 이 소멸할 때 각자의 레퍼런스 카운트를 감소시키고 결국 1번째가 std::shared_ptr<T>이 지워진 후 2번째 std::shared_ptr<T>이 소멸되는 순간 자료가 소멸되며 댕글링포인터에서 발생하는 문제가 동일하게 발생할 수 있습니다.

이러한 문제점은 C++ 유저로서 왜 이렇게 구현되었는지 이해가 되는 부분입니다. 만약 std::shared_ptr<T>내부에서 스마트포인터로 관리되는 클래스들에 자료구조를 만들어서(Map이나 set계열의 자료구조로 관리되는 데이터들의 주소 값과 레퍼런스 카운트를 연동하는 방식 이 경우에는 스마트포인터가 만들어질 때마다 이 데이터가 스마트포인터로 관리되는지 검색한다) 관리하거나 한다면 이는 스마트 포인터가 만들어 질때마다 상당한양의 연산을 사용하게 될 것입니다. 이런 점을 해결하기 위해서는 자신이 만드는 프로그램에서만 특수하게 적용하는 자신만의 스마트포인터 방식을 만들어보는 경험도 중요합니다.

이에 대해서는 2부에서 집중적으로 이야기해 보도록 하겠습니다.