C++의 포인터
포인터란?
포인터의 개념은 생각보다 간단한데 포인터란 ‘주소값을 저장하는 변수’이다. int 자료형이 ‘정수를 저장하는 변수’이고 float이 ‘실수를 저장하는 변수’인 것과 마찬가지로 포인터는 주소값을 저장하는 것이다. 이제부터 예시를 들며 하나씩 알아보자.
int a=10;
예를 들어 이렇게 int형 변수 하나를 만들어 값을 지정했다고 가정하자. 그러면 Cpu에서 알아서 RAM의 메모리 일부를 임의적으로 할당하여 10의 값을 저장해준다. 포인터는 이렇게 할당된 메모리의 주소값을 저장하는 변수이다. 그런데 이 변수에 할당된 메모리를 어떻게 알 수 있을까? 그를 위해 주소 연산자 ‘&’이 존재한다.
int a=10;
cout << &a; //주소값 출력
주소 연산자 ‘&’은 뒤에 붙은 변수의 주소값을 가진 포인터를 반환한다. 하지만 이렇게 얻은 주소값만으로는 아무 쓸모가 없다. 우리는 주소값의 메모리에 저장된 데이터를 읽어낼 수 있어야 한다. 그를 위해 역참조 연산자 ‘*‘이 존재한다.
int a=10;
cout << &a; //주소값 출력
cout << *&a; //10 출력
역참조 연산자 ‘*‘은 주소값 앞에 붙어 해당 주소값에 저장된 데이터를 반환한다. 포인터 변수 선언에 쓰이는 *과 같은 기호이기 때문에 헷갈리기 쉽지만 엄연히 다른 연산자이다.
포인터는 일반 변수와 같이 선언되지만 자료형 뒤에 *이 붙는다.
int a=10;
int* ptr=&a; //a의 주소값으로 초기화된 int형 포인터 변수 ptr
cout << *ptr; //a의 주소값에 담긴 데이터 10을 출력
아까 배웠던 주소 연산자와 역참조 연산자를 활용하여 포인터를 사용할 수 있다. 역참조 연산자를 활용하여 포인터를 역참조하면 포인터의 자료형을 참고하여 포인터의 주소값 내의 데이터를 해석하여 반환한다. 그러므로 포인터는 무조건 자료형을 가져야 하며 내부 데이터와 포인터의 자료형을 일치시켜주어야 한다.
포인터의 특징
포인터는 기본적으로 아키텍처에 기반한 크기를 가지게 된다. 32비트 OS에서는 32비트 메모리 주소를 사용하므로 포인터의 크기도 32비트가 된다. 64비트 OS에서는 마찬가지로 포인터의 크기도 64비트가 된다.
이러한 포인터는 추후 포스팅할 new/malloc 을 이용해 메모리를 동적으로 할당할 때의 반환형으로 사용되게 된다. 또한 많은 양의 데이터를 함수의 매개변수로 사용하고 싶을 때 데이터가 담겨있는 주소값을 함수에 전달하는 것으로 데이터의 복사 없이도 원하는 작업을 수행할 수 있다.
널 포인터(Null Pointer)
포인터는 선언될 때 기본적으로 임의의 쓰레기 값을 가지고 선언된다. 그러므로 꼭 필요한 값을 넣어 초기화 해주거나 Null 값을 넣어 아무것도 가리키지 않는 ‘널 포인터’로 만들어 주어야 한다.
댕글링 포인터(Dangling Pointer)
포인터는 방금까지 배웠듯이 주소값을 담는 변수이고 우리는 이를 역참조하여 주소값 내부의 데이터를 얻을 수 있다. 그런데 만약 메모리가 할당 해제된 주소값을 역참조 한다면 어떤 일이 벌어질까?
int a=10;
int* ptr1=&a;
int* ptr2=&a;
free(ptr1);
cout << *ptr2;
위의 코드를 살펴보자. int형 포인터 2개를 선언하여 같은 주소값을 가리키도록 하였다. free 함수는 포인터를 매개변수로 받아 해당 포인터에 할당된 메모리를 해제한다. free 함수로 두 포인터 중 하나를 할당해제하고 나머지 하나를 역참조하여 출력하였다. 무슨 일이 벌어질까? segmentation fault 및 다양한 예측 불가능한 결과를 얻을 수 있다. 이렇게 할당 해제된 메모리를 가리키는 포인터를 댕글링 포인터(Dangling Pointer)라고 한다. 이를 방지하기 위해 free 함수를 대체할 다른 함수를 작성하는 등 여러 예방 방법이 있다.
이중 포인터
포인터는 메모리 주소를 저장하는 변수이다. 그렇다면 포인터의 메모리 주소를 저장하는 포인터도 가능하지 않을까? 이중 포인터를 선언하면 물론 가능하다.
int a=10;
int* ptr1=&a;
int** dptr1=&ptr; //이중 포인터 선언
cout << **dptr1; //두번 역참조하여 10을 출력
포인터의 포인터를 선언하면 이중 포인터가 되어 포인터의 메모리 주소를 저장할 수 있게된다. 사용 방법은 기본 포인터와 같다.