포인터(Pointer)

이인혁·2024년 5월 5일

C

목록 보기
8/23

1. 포인터란?

포인터는 프로그래밍 언어에서 다른 변수, 혹은 그 변수의 메모리 공간주소를 가리키는 변수를 말합니다. 포인터가 가리키는 값을 가져오는 것을 역참조라고 합니다.

int a; //메모리 공간에 4byte짜리 정수 저장하는 공간을 할당해줘! 그리고 그 공간의 이름을 a라고 하겠어!

C언어에는 메모리공간의 주소값을 다루는 DataType을 가지고 있습니다. 그 DataType을 포인터형이라고 합니다. (포인터는 어딘가를 가르킨다는 의미입니다.)

쉽게 말하면, 변수를 선언하면 메모리에 변수의 DataType만큼의 공간의 할당받습니다. 포인터형은 그 메모리 공간상에 변수의 메모리 주소값을 저장하는 DataType이라고 생각하면 됩니다.

2. 포인터형지정자, 포인터연산자(asterisk)

많은 사람들이 포인터를 어려워하는 이유 중 하나가 포인터형지정자포인터연산자의 구분입니다. 그래서 포인터형지정자로 사용했음에도 포인터연산자의 성질대로 해석하여 오류를 범하곤 합니다. 포인터의 이해를 위해서는 먼저 둘의 차이를 명확히 구분할 필요가 있습니다.

먼저 포인터형지정자입니다. 포인터형지정자는 포인터형변수를 만들때 앞에 붙는 *를 의미합니다. 여기서 중요한 사실은 변수를 만들때입니다. 그렇습니다. 보통 변수선언할 때 붙는 *가 포인터형 지정자입니다. 물론 함수에 들어가는 매개변수에도 해당이 되는 얘기입니다.

근데 보통이라고 말한 이유가 있습니다. 왜냐하면 포인터형지정자는 형변환할때도 사용합니다. 대표적으로 나중에 배울 malloc함수에서 많이 사용합니다.

int* b; //int*(int형 포인터)int값이 저장된 공간의 주소값
b = &a; //&a는 a변수의 주소값을 반환하는 주소연산자입니다.
int** c; //int**(int형 포인터 포인터)int값이 저장된 공간의 주소값을 저장하는 공간의 주소값
c = &b
int*** d; //포인터형 변수를 만들 때 사용하는 *(asterisk)는 포인터형지정자
d = &c;

포인터연산자는 말 그대로 연산자입니다. 그 역할은 주소값과 만나면 그 주소에 있는 공간을 의미하게 됩니다.

***d; //주소값 앞에 사용하는 *(asterisk)는 포인터연산자
***&c;
**c;
**&b;
*b;
*&a;
a;

위의 7개의 식은 모두 같은 값을 나타냅니다. 일단 연산자에 대해 깊게 이해할 필요가 있습니다.

예를 들어 a+b 라는 연산을 할 때 우리는 수학적으로 a와 b를 더한다고 생각합니다. 하지만 프로그래밍적으로 생각한다면 a에 저장되어있는 '값'과 b에 저장되어있는 'b'값을 더한다고 생각할 수 있습니다. 여기서 핵심은 값입니다. 포인터연산자도 연산자이기 때문에 값에 대한 연산이 이루어 지게 됩니다.

일단 위의 식에서 ***d부터 보겠습니다. d앞에 연산자가 붙었으므로 d의 값을 불러오게됩니다. d에는 c의 주소값 즉 &c가 들어있습니다. 따라서 ***&c와 같습니다. 근데 포인터연산자는 주소값과 만나면 그 주소에 있는 공간을 의미하게 되기 때문에 **c와 같습니다. 또 c의 값은 &b이므로 **&b와 같고 이것 또한 *b와 같습니다. 포인터연산자는 이런식으로 하나씩 풀어나가면 이해하기 쉬워집니다.

그럼 이게 정말 같은지 출력을 한 번 해보겠습니다.

예시

#include <stdio.h>
int main() {
	int a; 
	a = 20;
	int* pa;
	pa = &a;
	int** ppa; 
	ppa = &pa;
	int*** pppa; 
	pppa = &ppa;
	printf("주소값을 출력\n");
	printf("&***pppa = %p\n", &***pppa);
	printf("&***&ppa = %p\n", &***&ppa);
	printf("&**ppa = %p\n", &**ppa);
	printf("&**&pa = %p\n", &**&pa);
	printf("&*pa = %p\n", &*pa);
	printf("&*&a = %p\n", &*&a);
	printf("&a = %p\n", &a);
	***pppa = 4000;
	printf("저장하고 있는 값을 출력\n");
	printf("***pppa = %d\n", ***pppa);
	printf("***&ppa = %d\n", ***&ppa);
	printf("**ppa = %d\n", **ppa);
	printf("**&pa = %d\n", **&pa);
	printf("*pa = %d\n", *pa);
	printf("*&a = %d\n", *&a);
	printf("a = %d\n", a);
	return 0;
}

결과

주소값을 출력
&***pppa = 000000ED87DAF794
&***&ppa = 000000ED87DAF794
&**ppa = 000000ED87DAF794
&**&pa = 000000ED87DAF794
&*pa = 000000ED87DAF794
&*&a = 000000ED87DAF794
&a = 000000ED87DAF794
저장하고 있는 값을 출력
***pppa = 4000
***&ppa = 4000
**ppa = 4000
**&pa = 4000
*pa = 4000
*&a = 4000
a = 4000

3. 간단한 포인터문제

지금까지 이해한 것을 바탕으로 직접 포인터코딩을 해보겠습니다.

문제1. a변수를 만들고 1000으로 초기화합니다. 그리고 a의 주소값을 저장하는 pa변수를 선언합니다. pa에 저장된 값을 저장하는 변수 pb를 만들고, pb변수로 a변수의 값을 10000으로 변경하고 a변수의 값을 출력해보세요.

코드

#include <stdio.h>
int main() {
	int a;
	a = 1000;
	int* pa = &a;
	int* pb = pa;
	*pb = 10000;
	printf("a = %d\n", a);
	return 0;
}

결과

a = 10000

문제2. 변수 a와 b를 선언하고 각각 20과 30으로 초기화시킵니다. 그리고 각각의 주소값을 저장하는 변수 pa pb를 선언합니다. 또 pa pb의 주소값을 저장하는 ppa ppb를 선언합니다. 변수 pa와 pb의 값을 바꿔서 **ppa가 b의 값을 **ppb가 a의 값을 가리키도록 해보세요.

코드

#include <stdio.h>
int main() {
	int a = 20;
	int b = 30;
	int* pa = &a;
	int* pb = &b;
	int** ppa = &pa;
	int** ppb = &pb;
    printf("**ppa = %d\n", **ppa);
	printf("**ppb = %d\n", **ppb);
	int* temp;
	temp = pa;
	pa = pb;
	pb = temp;
	printf("**ppa = %d\n", **ppa);
	printf("**ppb = %d\n", **ppb);
    return 0;
}

결과

**ppa = 20
**ppb = 30
**ppa = 30
**ppb = 20

문제3. 변수 a와 b를 선언하고 각각 20과 30으로 초기화시킵니다. 그리고 각각의 주소값을 저장하는 변수 pa pb를 선언합니다. 또 pa pb의 주소값을 저장하는 ppa ppb를 선언합니다.(2번과 동일) 변수 ppa와 ppb의 값을 바꿔서 **ppa가 b의 값을 **ppb가 a의 값을 가리키도록 해보세요.

코드

#include <stdio.h>
int main() {
	int a = 20;
	int b = 30;
	int* pa = &a;
	int* pb = &b;
	int** ppa = &pa;
	int** ppb = &pb;
	int** temp;
	printf("**ppa = %d\n", **ppa);
	printf("**ppb = %d\n", **ppb);
	temp = ppa;
	ppa = ppb;
	ppb = temp;
	printf("**ppa = %d\n", **ppa);
	printf("**ppb = %d\n", **ppb);
	return 0;
}

결과

**ppa = 20
**ppb = 30
**ppa = 30
**ppb = 20

꼭 한번씩 스스로 해보시길 바랍니다. 포인터를 이해하시는데 도움이 되실겁니다.

profile
게임개발공부블로그

0개의 댓글