크래프톤 정글 주제별 탐구 - 포인터
정의
포인터(pointer)는 메모리의 주소값을 저장하는 변수의 유형을 뜻한다. 우리가 지금까지 사용해 온 정수형(int)변수, 문자형(char)변수와 크게 다르지 않다.
주소값이란?
주소값은 데이터가 저장된 메모리의 주소를 뜻하며, 일반적인 경우 메모리의 시작 주소를 의미한다. C언어는 주소값을 1바이트 크기의 메모리 공간으로 나누어 표현한다(주소의 크기가 1바이트라는 것이 아니다! 주소는 x86_64 시스템에서는 8바이트를 가진다!).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main() {
int x = 10;
int *ptr = &x;
// 포인터의 크기 출력
printf("Size of pointer: %zu bytes\n", sizeof(ptr));
// 포인터가 가리키는 주소 출력
printf("Address of x: %p\n", (void*)ptr);
return 0;
}
// Output example
// 00000015c01ff844
포인터 연산자
C언어에서 사용하는 포인터 연산자는 다음과 같다.
- 주소 연산자(&)
- 참조 연산자(*)
주소 연산자는 변수의 이름 앞에 붙임으로써 사용할 수 있고, 해당 변수의 주소값을 반환한다. 앰퍼샌드라고 읽고, 번지 연산자 라고도 한다.
참조 연산자는 포인터의 이름이나 주소 앞에 사용하고, 포인터에 가리키는 주소에 저장된 값을 반환한다. (단, 선언 시에 사용하면 포인터를 선언할 수 있고, 메모리에 접근할 때에도 사용한다.)
포인터의 선언
포인터는 참조 연산자를 통한 선언을 통해 만들 수 있다. 아래는 C에서 정수(int)형 변수 a에 대한 포인터(int *) a_ptr를 만든 모습이다.
1
2
3
4
5
6
7
#include <stdio.h>
int main()
{
int a = 10;
int* a_ptr = &a;
}
또한, 포인터의 크기는 시스템, CPU 등에 의해서 바뀌며, 컴파일러의 정책에 따라 변하게 된다.
포인터 연산
포인터는 제한된 연산들만이 가능하며, 다음과 같은 규칙들을 따른다.
- 포인터끼리의 덧셈, 곱셈, 나눗셈은 의미가 없다.
- 포인터끼리의 뺄셈은 두 포인터 사이의 상대적 거리를 나타낸다.
- 포인터에 정수를 더하거나 뺄 수는 있지만, 실수와의 연산은 허용하지 않는다.
- 포인터끼리 대입하거나 비교할 수 있다.
이때, 포인터의 덧셈에서 재미있는 부분이 있다. int형 포인터 ptr을 선언하게 되었을 때, ptr = ptr + 1을 하게 되면, 실제 ptr의 값보다 1 큰게 아닌, 선언한 변수 int형의 크기만큼 증가하게 된다.
인수 전달하기
함수를 호출할 때 필요한 데이터를 전달하는 방식이 pointer를 사용하여, 참조에 의한 전달을 통해 가능한 방법이 있다.
값에 의한 전달은 이전처럼 사용하거나, pointer에 참조 연산자(*)를 붙여서 전달 가능하다.
참조에 의한 전달은 값에 주소 연산자(&)를 붙이거나, pointer를 통해 전달할 수 있으며, 이를 통해 값을 바꾸게 되면 원래 함수로 돌아와도 값이 바뀌어 있다!(메모리를 통해 직접 변수에 접근해서 바꾸기 때문이다.)
이중 포인터
이중 포인터는 포인터에 참조 연산자(*)를 붙여서 포인터의 주소를 가르키는 변수이다. 이는, 배열 등을 설정하고 활용 할 때 이중 포인터의 형태로 넘겨주게 되는 경우 등에서 사용한다.
Void 포인터
일반적으로 malloc의 경우, 크기에 따라 메모리 공간을 할당하고 그의 첫번째 주소를 반환한다. 이는 어떠한 형이 와도 전달해야 하므로(근데 모든 형의 주소값이 다를 수도 있으니까…?) void 형의 주소를 전달하지만, 실제로 활용해보면 비슷했다…
근데 void 형 포인터는 일반적으로 어떠한 변수, 함수, 포인터 등을 가리킬 수 있지만, 포인터 연산이나 메모리 참조는 불가능하다고 한다. 그래서 void 포인터는 사용할 때마다 명시적 타입 변환을 하고 난 뒤에 사용해야 한다고 한다.
함수 포인터
void 포인터에서 잠시 이야기했지만, 그렇다. 함수도 어딘가에 저장되어 있으니, 함수 역시 포인터로 불러올 수 있다!
NULL 포인터
이거는 별거 없긴 한데… 0이나 NULL로 초기화한 포인터를 NULL 포인터라고 한다.
추가적으로…
포인터는 C에서 사용해보면 배열과 유사하다! 직접 Queue나 Stack을 구현해보면 그 유사성을 더 잘 파악할 수 있을지도 모른다!