포인터는 메모리의 주소값을 저장하기 위한 특별한 변수 입니다.
변수를 선언하면 메모리 공간을 할당하고 할당된 공간에 값을 저장하여 사용할 수 있습니다.
메모리는 고유한 주소값을 갖게 됩니다.
변수에 &(주소연산자)를 사용하면 해당 변수의 주소를 읽을수 있습니다.
#include <stdio.h>
int main(void) {
char ch1 = 'A', ch2 = 'B';
int num = 10;
printf("%p\n", &ch1);
printf("%p\n", &ch2);
printf("%p\n", &num);
return 0;
}
위의 코드를 실행하면 변수의 주소를 볼수 있습니다.(실행할때 마다 매번 주소가 바뀝니다.)
변수가 할당된 형태를 메모리상에서 표현된것을 상상해 본다면 다음과 같을 수 있습니다.
문자 'A'가 저장되어 있는 변수 ch1의 주소값은 0x33FB77 에 할당되고, char형 임으로 1byte크기를 갖고
문자 'B'가 저장되어 있는 변수 ch2의 주소값은 0x33FB78 에 할당되고, char형 임으로 1byte크기를 갖고
그렇다면 int형 변수 num은 어디에 할당되 있나요?
"int형 변수 num은 0x33FB79 에서 부터 0x33FB78C 까지 할당되어 있습니다."
하지만 C언어에서는 시작 번지만을 가지고 위치를 표현합니다.
int형 자료는 4byte 임으로 시작위치를 알면 끝이 어딘지는 쉽게 계산이 가능하기 때문입니다.
"int형 변수 num은 0x33FB79 할당 되어있습니다." 라고 말할수 있습니다.
포인터 변수는 메모리의 주소값을 저장하기 위한 변수 이므로 0x33FB79와 같은 메모리의 주소값을 저장할 수 있습니다.
포인터는 *(간접지정연산자)를 이용해 표현하며 다음과 같이 선언합니다.
자료형 * 변수이름;
포인터변수의 자료형은 포인터가 가리키는 값의 크기를 나타냅니다.
#include <stdio.h>
int main(void) {
int num = 10;
int * ptr; // int형 변수의 주소값을 저장하는 포인터 변수 선언
ptr = # // num의 주소를 포인터에 저장
printf("num의 주소값 : %p\n", &num);
printf("포인터 ptr값 : %p\n", ptr);
printf("ptr이 가리키는 값 : %d\n", *ptr);
num = 20; // num의 값을 변경;
printf("ptr이 가리키는 값 : %d\n", *ptr);
*ptr = 30; // 포인터가 가리키는 값을 변경
printf("num 값 : %d\n", num);
return 0;
}
포인터 변수로 값을 참조하려면 *(간접지정연산자)를 사용하여 값을 참조할 수 있습니다.
#include <stdio.h>
int main(void) {
int num = 10;
char c = 'A';
double pi = 3.14;
int * pnum = # // int형 포인터
char * pc = &c; // char형 포인터
double * ppi = π // double형 포인터
printf("int형 포인터의 크기 : %d\n", sizeof(pnum));
printf("char형 포인터의 크기 : %d\n", sizeof(pc));
printf("double형 포인터의 크기 : %d\n", sizeof(ppi));
return 0;
}
포인터는 모두 4바이트의 크기를 갖고 있는걸 알 수 있습니다.
우리가 처음 프로젝트를 생성할때 32비트 콘솔 응용프로그램을 사용해서 만들었기 때문에 32bit 즉 4바이트의 주소체계를 갖는 응용프로그램이기 때문입니다.
과거 16bit 컴퓨터에서는 2바이트 주소체계를 사용했습니다.
요즘은 64bit의 컴퓨터를 사용하지만 우리가 현제 작업하는것은 32bit 환경이기 때문에 4바이트를 갖고 실제 64bit 시스템에서 동작하는 컴파일러로 컴파일시 8바이트의 크기를 갖는 포인터를 사용할 수 있습니다.
그렇다면 포인터의 자료형은 왜 구분을 할까요?
포인터의 자료형은 메모리 공간을 참조하는 크기의 기준이 됩니다.
int형 포인터 자료형은 4byte를 참조하고, double형 포인터는 8byte를 참조하게 됩니다.
#include <stdio.h>
int main(void) {
double a = 3.14;
int * p = &a; // 자료형이 불일치
printf("%d\n", *p); //예측불가능한 의미없는 출력
return 0;
}
8byte인 double형 변수 a를 int형 포인터 변수 p로 4byte만을 참조하기 때문에 6행의 출력은 알수 없는 의미없는 값을 출력합니다.
포인터를 선언만 하는 경우 사용할 수 없는 쓰레기 값으로 초기화 됩니다.
int * ptr; // ptr은 쓰레기값으로 초기화 됨
잘못된 쓰레기 값을 가지고 있는 포인터로 연산을 할 경우 프로그램이 멈추는 현상도 일어날 수 있고, 잘못된 메모리에 접근에 대한 보호장치가 없는 운영체제의 경우 치명적인 영향을 줄 수도 있기 때문에 포인터는 항상 나중에 값을 넣더라도 초기화 하는게 좋습니다.
이때 포인터에 0값을 대입하여 초기화하고 이는 메모리주소의 0번지를 가리키는게 아닌 아무곳도 가리키지 않는다는 의미로 해석되며 이런 포인터를 널포이터 라고 합니다
다음과 같이 초기화 하세요.
int * ptr = 0; // ptr은 쓰레기값으로 초기화 됨
int * ptr = NULL // NULL은 stdio.h 에서 정의된 매크로 상수
#include <stdio.h>
int main(void) {
int num = 10;
int *ptr1 = #
int *ptr2 = ptr1;
(*ptr1)++;
(*ptr2)++;
printf("%d\n", num);
return 0;
}