본문 바로가기

KUCC/C언어 세션

[C언어세션][포인터] 포인터란, 포인터 연산자( * , &), 포인터형

이번 시간에는 포인터에 대해 배워보겠다.

C언어에서는, 포인터를 이용하면 메모리에 직접 접근이 가능하다!

 

포인터 변수란?


포인터를 잘 이해하기 위해서는 이렇게 메모리 공간을 그림으로 그려가면서 공부해야한다.

num1의 메모리 주소 값은 008F7FC이다. 1바이트의 메모리 공간을 단위로 하나의 주소값이 할당된다.

이것도 저장가능한 값이고, 이를 저장하기 위해 마련한 변수가 "포인터 변수이다".

 

 

 

char형 변수는 1바이트만 필요로 한다.  해당 1바이트가 차지하는 메모리 주소에 할당되어있다고 하면 된다. 하지만 int형 변수는 어떨까? int형 변수는 4바이트다. 하지만 C언어에서는 시작 위치만 이야기한다.

int 형 변수 num이 0xl2ff76번지에 할당되어있다고 하면, 실제론 0xl2ff76부터 0xl2ff79에 걸쳐 할당되어 있다는 뜻이다. 그리고 이때, 주소값 0xl2ff67도 마찬가지로 정수이다.

 

 

 

포인터 변수와 &연산자


int main(void){

   int num = 7;
   int * pnum;
   pnum = #

}

해당 코드는 다음과 같이 해석될 수 있다. 정수 7이 저장된 int형 변수 num 을 선언하고, 이 변수의 주소값 저장을 위한 포인터 변수 pnum을 선언하자. 그리고 나서 pnum에 변수 num 주소값을 저장하자.

int * pnum; 이 문장이 포인터 변수의 선언이다.

int * 은 int형 변수의 주소값을 저장하는 포인터 변수의 선언이다.

pnum은 포인터 변수의 이름이고.

 

또, &연산자를 살펴보자. &연산자는 오른쪽에 등장하는 피연산자의 주소값을 반환하는 연산자이다. 즉, 위의 사례에서는 num의 주소값을 반환한다.

 

이때, 포인터 변수의 크기는 얼마일까? int형 변수 num의 크기는 4바이트인데, 포인터 변수의 크기는 4바이트가 될 수도 있고, 8바이트가 될 수도 있다. 왜? 32비트 시스템에서는 4바이트이고, 64비트 시스템에서는 8바이트이다.

 

포인터 변수 선언하기


포인터 변수는 변수의 자료형에 따라 선언방법이 달라진다. 사실상 주소값은 모두 정수의 형태를 띠지만, 가리키는 변수의 자료형에 따라 포인터 변수를 선언하는 방법이 달라진다.

 

int * pnum; 의 경우, int *는 in형 변수를 가리키는 pnum을 선언하는 것이다.

마찬가지라 double * pnum ...등등이 있다.

 

(가리키는 변수의 자료형)  * (포인터 변수 이름) ; 식으로 포인터 변수를 선언하면 된다.

 

이런식으로 포인터의 자료형이 정해진 포인터 변수도 있지만, C 언어에서는 자료형이 정해지지 않은 포인터도 있고, 이는 void 포인터라는 포인터인데 void * (포인터 변수 이름)식으로 선언하면 된다.

 

이때, (가리키는 변수의 자료형) * 은 포인터형이라고 부르기도 한다. 이 또한 '자료형'이다.

 

자료형이 다른 포인터끼리 메모리 주소를 저장하면 컴파일 경고가 발생한다. 하지만 void 포인터는 자료형이 정해지지 않은 특성 때문에 어떤 자료형으로 된 포인터든 모두 저장 할 수 있다. 반대로 다양한 자료형으로 된 포인터에도 void 포인터를 저장할 수도 있다. 이런 특성 때문에 void 포인터는 범용 포인터이다.

 

그리고 참고로 말하자면 double * ptr 이나, double* ptr이나, double *ptr이나 다 같다. (띄어쓰기 상관없음)

 

 

&연산자


&변수는 한마디로 피연산자의 주소값을 반환하는 연산자이다. 따라서, &연산자의 피연산자는 상수가 아닌 변수여야 된다. 그리고, 자료형에 맞지않는 포인터의 변수는 문제가 될 수 있다.

int main(void){

   int num1 = 5;
   double *pnum = &num1;

}

위의 상황은 문제가 된다.

 

*연산자


*연산자는 포인터가 가리키는 메모리를 참조한다. 포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 접근하는 역할을 하는 것이다.

그래서 사실상, *pnum은 포인터 변수 pnum이 가리키는 메모리를 참조하는 것이기 때문에 변수 num을 참조한다는 것과 똑같은 의미이다.

#include <stdio.h>

int main(void){

   int num1 = 100, num2 = 100;
   int * pnum;
   
   pnum = &num1;
   (*pnum)+=30;
   
   pnum = &num2;
   (*pnum)-=30;
   
   printf("num1 : %d , num2 : %d",num1,num2);
   return 0;


}

위 코드를 해석해보자. pnum은 정수형 변수를 가리키는 포인터 변수이다. 처음에는 이 포인터 변수에 num1의 주소값을 반환하도록 하여 num1을 가리키게 한다. 이때, *pnum을 하면 num1에 접근하게 된다. 그리고 num1의 값을 30만큼 증가시킨다. 다음에는, pnum이라는 포인터가 num2를 가리키도록 한다. 그리고 *pnum을 하면 num2에 접근하게 된다. num2를 30만큼 감소시킨다. 그리고 출력을 하면 num1은 30증가된 상태로, num2는 30 감소된 상태로 출력이 된다.

 

이때 중요한 것은, pnum이 가리키는 대상이 num1에서 num2로 바뀌었다는 점이다. 어떻게? &기호로 선언을 해주었기 때문에.

 

포인터의 잘못된 사용


int main(void){

   int * ptr;
   *ptr = 200;


}

포인터 변수 ptr을 선언만 했지, 초기화를 해주지 않은 상태에 200을 저장해주었따. 이런 경우 치명적인 결과로 이어질 수 있다. int * ptr = 125; 이런식으로 초기화하는 것도 문제가 된다. 쓰레기 값으로 초기화 한 것 밖에만 안된다. 125가 어떤 변수의 주소값인 걸 알 수 없잖아!!!

 

그렇기 때문에 널 포인터가 필요하다. 0 이나, NULL로 초기화를 해놓는 것이다.

int * ptr = 0 이나 int * ptr = NULL; 이런식으로 초기화 해놓자.

이건 실제로 0이라는 주소값을 의미하는게 아니라, 아무데도 가리키지 않는다는 뜻이다.따라서 메모리 공간에 어떠한 영향도 미치지 않는 초기화 방법이다.

 

다음 시간에는 좀 더 어렵게 느껴질 수 있는 포인터와 배열, 포인터와 함수에 대해 배워볼 것이다.

이번 시간 내용을 꼼꼼히 잘 이해하고 암기해시길 바란다.