본문 바로가기
프로그래밍/공부

[C++] 포인터

by Sik.K 2023. 5. 4.

다시 한 번 복습할 기회가 생겨서 이렇게 정리를 해보게 된다.

 

포인터란 무엇일까?

 

C언어에 존재하는 포인터라는 자료형은 특이하게도 주소값을 담는 변수이다.

 

우리가 정적 변수든, 동적할당을 한 변수든 변수는 메모리 공간에 주소를 할당 받는다.

 

int a = 10;

위처럼 선언하면 메모리 공간에 int 자료형의 크기 만큼 a라는 변수가 공간을 할당 받게 되고 그 안에 10이라는 값이 담기게 되는 것이다.

 

하지만 포인터는 주소값을 내부 값으로 가지는 변수다. 이 말이 무엇을 의미할까.

 

 

int a = 10;
int* b = &a;

위의 코드의 내용은 결국 메모리 공간에 포인터 변수 b의 공간을 할당하고 그 내부 값으로 a의 주소를 담아두겠다는 말이다. 때문에 b에 접근하면 b는 a의 주소를 알고 있으므로 가지고 있는 주소로 가서 그 내부 값을 참조할 수 있다.

 

따라서, b를 통해 a의 값을 수정할 수 있는 것이다.

 

이렇게 포인터 변수는 주소를 담는 변수라는 것을 알 수 있었다. 그렇다면 포인터는 왜 사용하는 것일까? 그 이유는 동적할당에 있다.

 

동적할당 시 포인터 변수

 

특정 클래스를 만들어 두고 선언을 할 때, 동적할당을 위해 우리는 포인터 변수를 미리 선언해두고 그 내부에 동적할당 된 주소를 담는다.

 

TempClass* temp = nullptr;
temp = new TempClass();

위 코드를 해석하자면, TempClass라는 클래스의 주소를 담기 위한 포인터 변수 temp를 선언해두고 초기값은 nullptr로 초기화를 해둔다. 그 다음 temp에 메모리 공간 어딘가에 new 키워드를 통해 새롭게 할당된 TempClass의 객체의 주소를 담아준다.

 

여기서 핵심은 어딘가이다. 우리는 사전에 공간을 할당하지 않았다. 새롭게 할당한 공간이다. 동적할당으로 할당 받은 공간은 런타임 시에 정해지며 우리는 그 공간을 변수로 지정하지 않았기 때문에 접근할 수 없다.

 

이 때문에 포인터를 사용하는 것이다. 포인터 변수를 미리 선언해두고 런타임 시 할당 받은 객체의 주소만을 담아주기 때문에 우리는 포인터 변수를 통해 해당 객체의 주소가 어디든 접근할 수 있는 것이다.

 

이와 마찬가지로 해제를 할 때도 직접 객체에 접근하는 것이 아니라 객체의 주소를 담고 있는 포인터 변수에 접근하여 해제를 해주면 된다.

 

 

댕글링(허상) 포인터

 

 

문제는 여기서 발생한다. 만약 특정 객체의 주소를 가지고 있는 포인터 변수를 통해 객체를 동적할당 했다면 어떤 일이 일어날까?

 

새롭게 생성했던 객체는 사라졌다. 하지만 temp는 여전히 어딘가를 가리키고 있다. 이 점이 바로 문제가 된다. 포인터 변수는 단순히 주소를 담고 있는 값이다. 포인터 변수가 주소로 접근하여 참조를 할 때는, 자신이 취하고 있는 자료형의 크기만큼 그 주소로부터 시작해서 읽어들이는 것이다.

 

만약 객체가 동적 할당해제가 되고 이후에도 포인터 변수가 그 자리를 가리키고 있다면 어떤 일이 일어날까? 답은 뻔하다. 그 자리에 어떤 데이터가 들어와도 혹은 그 자리를 포함하는 크기로 데이터가 들어와도 temp는 여전히 특정 어딘가를 가리키고 있다. 하여 그곳에 접근을 하여 값을 읽어오고 싶어도 읽어올 수 없는 오류가 발생한다.

 

이 문제를 댕글링 혹은 허상 포인터라고 한다. 이 문제를 해결하기 위해서는 포인터 변수를 통해 객체를 동적할당 해제를 하였으면 반드시 nullptr로 값을 초기화시켜야 한다.

 

 

스마트 포인터

 

 

하지만 일일이 그런 행동을 하는 것이 불편하기 때문에 간혹 사용되는 것이 바로 스마트 포인터이다. 스마트 포인터란, 해당 포인터 변수가 가리키고 있는 주소의 데이터가 더이상 사용되지 않을 경우 자동으로 동적할당 해제를 해주는 포인터다. 이름대로 똑똑한 포인터라고 생각하면 된다.

 

C언어는 동적할당을 했다면 반드시 사용자가 동적할당 해제를 해줘야 한다. 이는 다른 언어와는 차이가 있는데 다른 언어는 가비지 컬렉터가 있는 언어들이 많아 별도로 신경을 써야할 필요가 없다.

 

하지만 C언어에서는 동적할당 해제를 하지 않으면 메모리 공간에 계속 남게 되어 퍼포먼스의 저하를 불러온다. 가령 이미 어떤 주소를 가리키던 포인터 변수에 다른 주소를 넣게 되면 기존에 가리키고 있던 주소에 남아 있는 데이터가 동적할당이 자동으로 되지 않아 계속 자리를 차지하고 있는 것이다.

 

이런 문제를 해결하기 위해 모던 C++에서는 스마트 포인터라는 것을 도입했고 여기에는 unique_ptr, shared_ptr, weak_ptr이 있다.

 

이 부분에 대해선 차후에 스마트 포인터에 대한 포스팅을 올리면서 제대로 공부하고자 한다.

'프로그래밍 > 공부' 카테고리의 다른 글

[Graphics] 3D 환경에서 회전  (1) 2023.05.16
[Graphics] 정점, 폴리곤, 메시  (0) 2023.05.08
[C++] & Reference  (0) 2023.04.22
[C++] Lvalue & Rvalue  (0) 2023.04.22
[C++] 바이트 패딩  (0) 2023.04.20

댓글