article thumbnail image
Published 2020. 5. 19. 11:44

포인터란 무엇일까?

"어떤 것을 가리키는 것" 이라고 유추할 수 있다.

맞다. 포인터는 "주소"를 가리킨다.

이름만 포인터라고 다를 뿐이지 int,char 같이 다를 바 없는 변수이다.

포인터 변수라고 부르기도 한다.

변수면 어떤 값을 저장하는 것일텐데, 포인터는 무엇을 저장할까?

주소값을 가리킨다고 했으니, 당연히 주소값을 저장한다.

int형은 정수를, char는 문자를 저장하듯이 포인터는 변수의 주소값을 저장한다.

 

코드를 한번 직접 봐보자.

위와 같이 포인터 변수를 선언할 때는 담고자 하는 자료형에 *(참조 연산자)를 붙여서 선언한다.

만약 int형 변수 주소를 담고 싶으면 int*를, char형 변수의 주소를 담고싶다면 char*을 사용하는 것이다.

그렇다면 자료형에 따라 주소값의 크기도 달라지는 것일까?

 

사실, 포인터 변수의 크기는 모두 동일하다. 동일한 운영체제 시스템일 경우 주소값이 동일한 크기를 갖기 때문이다.

32비트 시스템이면 4바이트, 64비트 시스템이면 8바이트로 말이다. 그런데 자료형에 따라 선언하는 자료형이 달라지는 이유는, 가리킬 주소가 어떤 자료형을 갖는지 알려주기 위해서이다. 포인터 연산을 할 때에는 그 주소로 찾아가 int형이면 4바이트만큼, double형이면 8바이트씩 읽어들여야 한다. 따라서 어떤 자료형의 주소를 가리키는지 알려줘야 하는 것이다. 그냥 복잡하게 생각할 필요 없이, 가리키는 변수에 맞춰 포인터 변수도 자료형을 맞춰 준다고 생각하자.

 

주석으로 달아 놓듯이 아래는 모두 같은 코드이다. *의 위치는 상관이 없다. 맘에 드는 형태로 사용하면 된다.

int  *p

int*  p

int  *  p

이것으로 포인터를 선언했다.

 

그리고 한가지 알아두어야 할 점이 있다. int나 double 같은 경우는 정수형이기 때문에 처음에 우리가 원하는 숫자를 넣어서 초기화 해줄 수 있었다. int num = 10; 처럼 말이다. 하지만 포인터는 주소값을 담는 변수이기 때문에 어떤 특정한 숫자로 할 수 없다. 따라서 숫자가 아닌 NULL(0)로만 가능하다. 물론 NULL로 초기화하지 않고 그냥 선언만 한 후 주소값을 넣어도 괜찮다. 포인터를 초기화할 때 쓰는 0은 0번지를 가리키는 것이 아니라, 아무것도 없다는 뜻의 NULL(0) 이다.

 

하지만 변수를 초기화하지 않았을 때의 초기값은 쓰레기값이 들어가 있다는 사실을 기억하고 있나?

포인터 변수도 마찬가지이다. 특히 포인터는 주소값을 다루기 때문에 초기화를 하지 않고 사용하다가 실수로 잘못된 주소를 다루게 되면 프로그램이 예상치 못하게 종료되거나 다른 오류를 일으킬 수도 있다. 선언 후 바로 다른 변수의 주소값을 넣더라도, 되도록이면 NULL로 초기화를 해주는 것을 권장한다.

 

이제 선언한 포인터에 num 변수의 주소값을 담을텐데, 주소값을 넣을 때는 이미 잘 알고 있듯이 &를 붙여주면 된다. 선언할 때는 *p 로 해주었지만, 주소값을 넣을 때는 p = &num 형태로 * 연산자를 떼어준다. 포인터를 처음 접한다면 이해가 잘 안갈 수 있다. "분명히 *p 로 선언했는데 왜 변수를 사용할 때는 p로만 쓰지?"  *를 붙이게 되면 포인터 변수를 나타내는게 아니라, 다른 의미를 갖게 된다. 이렇게 생각하면 쉽다. 포인터도 변수이고 변수 이름은 p다.  

 

보는 것과 같이 num의 주소와 같은 값이 정상적으로 들어갔다.

이제 이 포인터 p는 변수 num을 가리키게 된다.

좀더 이해하기 쉽도록 그림으로 봐보자.

 

주소값(1000)은 좀더 보기 쉽도록 임의로 설정한 것이다. 보는것과 같이 포인터p에는 num의 주소값(1000)이 들어있고, 그렇게 되면 포인터 연산을 할 때 p에 들어있는 주소값(1000)으로 찾아가 그 값으로 연산을 하게 된다. 즉 num을 찾아가게 되는 것이다. 그런데 변수 p를 출력할 때 분명히 주소값으로 출력됐는데, 어떻게 num의 주소로 찾아가서 사용하는 것일까?

 

이것은 * 연산자에 달려있다.

 

* 연산자는 곱셈을 할 때 사용하기도 하지만 포인터를 이용할 때 쓰기도 한다. &연산자가 and 연산자와 주소 연산자로 쓰이듯, 마찬가지로 두개의 피연산자(a*b)가 있으면 곱셈연산자이고 하나의 피연산자만 있으면 참조연산자 이다. 참조 연산자는 포인터의 이름이나 주소 앞에 사용하며, 포인터가 가리키는 주소에 저장된 값을 반환한다.

 

num의 주소(&num)과 포인터 변수 p에 들어가 있는 값은 동일하다는 것을 확인할 수 있다.

참조연산자 * 를 사용하여 p가 가리키는 값을 출력하면 p에 들어있는 주소로 가서 그 변수의 값을 가져오게 된다.

 

 

문제는 (*p)++ 와 *p++ 이다.

증감 연산자가 참조 연산자보다 우선순위가 높아서

*p++ 는 주소를 먼저 찾아가지 않고 주소값이 들어있는 변수 p를 먼저 증가시킨다.

*(p++) 와 같다.

그러면 포인터 변수에 들어있는 주소값이 증가하는 것이다.  

 

(*p)++ 는 주소를 먼저 참조한 후 num값을 증가시킨다.

 

'📌 C' 카테고리의 다른 글

C - Call by value & Call by reference  (0) 2020.05.19
C - if 문의 동작 방식  (0) 2020.05.03
복사했습니다!