포인터는 매우 중요하며 어려운 개념이므로 차근차근 공부해야한다. 포인터를 이용하면 메모리에 직접 접근 가능하다.
포인터 변수는 주소값을 저장하는 변수이므로, 포인터 변수에 대해 공부하려면 먼저 메모리 주소 체계에 대해 알아야한다. 주소 cell 단위는 1바이트이다. 사전에 공부했던 대로 자료형에 따라 메모리 상에 차지하는 공간의 크기가 달랐다. 예를 들어 char형은 1바이트이므로 주소 블록 하나만 차지하고, int형은 4바이트이므로 블록 4개에 걸쳐 저장된다. 이 때, 데이터의 크기를 알기 때문에 할당받은 주소 블록 중 첫 번째 블록만을 가지고 위치를 표현하면 된다.
주소값은 양의 정수로 표현되는데, 이는 저장할 수 있는 데이터이다. 주소값을 저장하기 위한 변수가 포인터 변수이다.
다음 예시를 보자.
int num = 52;
int *pnum; //int형 포인터 변수 pnum
pnum = # //포인터 변수 pnum에 num의 주소값 저장
scanf함수에서도 사용했던 &연산자는 피연산자의 주소값을 반환하는 연산자이다. &num과 같이 사용하면 num의 주소값이 리턴되고, 그 주소값을 포인터 변수 pnum에 저장한 것이다.
64비트 체제 컴퓨터에서 포인터변수의 크기는 어떤 자료형에 대한 포인터 변수이던지 상관 없이 모두 8바이트이다. 하지만 선언문은 다르다.
int *p1; //int형 포인터 변수 p1
double *p2; //double형 포인터 변수 p2
unsinged int *p3; //unsinged int형 포인터 변수 p3
위와 같이 선언한다.
이 때, *의 위치는
int * pnum;
int* pnum;
int *pnum;
세 가지 모두 상관없다.
포인터형에 대하여
👉🏻포인터는 모두 8바이트라고 했다. 그렇다면 포인터 변수의 형을 명시하는 이유는 무엇일까?
포인터에 저장되는 것은 메모리 주소값인데, 어떠한 변수의 주소값은 첫 번째 주소값만을 가리키는 것이다. 첫 번째 주소만 알고 있으므로 그 첫 번째 주소값에서부터 몇 바이트를 읽어내야 하는지 알아야 데이터에 접근할 수 있다. 또한 정수와 실수의 구분도 해주어야 할 것이다.
& 연산자는 피연산자의 주소값을 리턴하는 연산자이며, 상수는 피연산자가 될 수 없고 변수만 가능하다.
* 연산자는 포인터가 가리키는 메모리를 참조한다. 예시를 보자.
#include <stdio.h>
int main(void) {
int num = 10;
int *pnum = #
printf("%d\n", pnum);
printf("%d", *pnum);
return 0;
}
19920960
10
위의 예시를 보면 pnum이 무엇을 의미하는지 알 수 있다. pnum에 변수 num의 주소값을 저장했고, 따라서 pnum을 출력했을 때 주소값이 출력된다.(프로그램이 실행될 때마다 메모리는 새로 할당되므로 num의 주소값은 달라진다.)
연산자 *를 붙여 *pnum을 출력하자 포인터 변수 pnum이 가리키는 메모리 공간에 저장된 10이 출력되었다.
pnum은 마치 num처럼 생각할 수 있고, num은 10이다.
👉🏻하나의 변수를 여러 포인터 변수가 가리킬 수 있다.
다음 예시를 보자.
int num = 10;
int* ptr1 = #
int* ptr2 = ptr1;
이러한 경우 두 포인터 변수 ptr1과 ptr2는 모두 int형 변수 num을 가리킨다.
❗포인터 변수를 선언과 동시에 초기화하지 않는 경우, 널로 초기화해주는 것이 적절하다. 이를 널 포인터라 부른다.
int *ptr1 = 0;
int *ptr2 = NULL;
위와 같이 초기화하면 포인터 변수는 아무데도 가리키지 않는다.
포인터 변수를 초기화하지 않으면 쓰레기 값으로 초기화되고, 이 주소는 어디인지 알 수 없다. 또한 포인터 변수를 임의의 정수로 초기화하면 심각한 문제를 야기할 수 있다.
&연산자 : 변수의 주소값을 리턴
*연산자 : 포인터가 가리키는 메모리 참조