9. 포인터 (1)
1. 포인터란?
포인터의 특징:
- 크기가 플랫폼 크기를 따라간다.
- 윈도우 64비트 플랫폼(x64)의 경우 8바이트, 윈도우 32비트 플랫폼(x86)의 경우 4바이트이다. (이후 특별한 언급이 없으면 x64의 포인터이다)
- 정수 표현체계를 따른다.
- 주소를 저장하는 변수이다.
- 선언된 자료형과 일치하는 포인터만 할당할 수 있다.
- 예를 들어, int 포인터에 double, float, short, long 등의 다른 자료형 포인터를 할당할 수 없다.
포인터는 변수의 주소를 저장하는 변수로, 포인터를 이용하면 함수 안에서 선언된 정적 변수에도 접근이 가능하다.
포인터는 선언 시 어떤 자료형의 포인터인지 명시된다.
int* pint = nullptr; //int 타입 포인터로 선언되고 초기화된다.
nullptr
은 포인터 변수용 0으로써 초기화에 사용되는 상수이다.
간접 참조 연산자 *
를 포인터 변수 앞에 붙여서 포인터 변수에 저장된 주소로 접근할 수 있다.
주소 반환 연산자 &
를 일반 변수 앞에 붙여서 해당 변수의 메모리 주소를 반환할 수 있으며, 이를 포인터 변수에 할당할 수 있다.
int a = 0; //변수 a를 선언하고 0으로 초기화한다.
int* pa = &a; //포인터 pa를 선언하고 변수 a의 주소값을 할당한다.
*pa = 20; //포인터 pa에 저장된 주소(변수 a의 주소)로 접근하여 20을 할당한다. 결과적으로, 변수 a에 20이 할당된다.
포인터는 변수의 주소에서 변수가 할당된 메모리의 “시작 주소”를 저장한다.
따라서 포인터의 간접 참조 동작 순서는 다음과 같다:
- 포인터에 저장된 메모리의 시작 주소로 이동한다.
- 포인터의 선언된 자료형의 크기만큼 자료를 확인한다.
- 포인터의 선언된 자료형에 따라 확인한 자료를 해석한다.
2. 포인터와 자료형
특정 자료형으로 선언된 포인터는 “나는 주소에 있는 값을 해당 자료형으로 읽을 것이다” 라고 미리 단정지어 두는 것이다.
그래서 기본적으로는 특정 자료형의 포인터는 해당 자료형에 해당하는 주소값만 받을 수 있다.
int i = 10;
short s = 3;
int* pint = &i; //int 포인터에 int 변수의 주소를 할당
short* pshort = &s; //short 포인터에 short 변수의 주소를 할당
pint = &s; //문법 오류: int 포인터에 short 변수의 주소를 할당할 수 없다
만약 A라는 주소에 가서 그 메모리를 읽는다면, A에 저장되어 있던 변수가 원래 무엇이든지간에 “선언된 자료형의 크기” 만큼 “선언된 자료형” 으로 해석한다.
A에 float 타입 변수가 있었다면 4바이트를 온전히 읽기는 하지만 정수로 해석되어 버리고, char 타입 변수가 있었다면 3바이트를 더 읽어버리는 것이다.
이러한 오류를 방지하기 위해 컴파일러는 서로 다른 자료형의 포인터에 값을 할당하려 하면 문법 오류를 띄운다.
이 때, 특이한 포인터가 하나 있는데, 바로 void 포인터 이다.
그러면 void 타입 포인터는 어떻게 읽어들이는가? 답은 “읽지 않는다” 이다.
void 로 해석하겠다는 뜻은 다시 말하면 그 어떤 것으로도 보지 않겠다는 것이다. 그래서 void 포인터는 어떤 자료형의 주소라도 다 할당될 수 있다.
하지만 void 포인터는 해석을 포기해버렸기 때문에 간접 참조 연산자 *
를 이용한 접근이 불가능하며, 다른 포인터 변수에도 (원칙적으로는) 할당이 불가능하다.
3. 포인터와 구조체
구조체 또한 사용자가 정의한 자료형이므로 구조체가 저장된 메모리의 시작 주소를 포인터에 저장할 수 있다.
포인터를 사용하여 구조체 자체의 주소에 접근하여 간접 참조를 하거나, 구조체 내부에 선언된 객체를 간접 참조할 수도 있다.
MyDataType data = {};
MyDataType* pdata = &data; //구조체의 포인터를 선언, 구조체 주소를 할당.
float* pf = &data.f; //구조체 내부 float 변수의 주소를 포인터에 할당.
(*pdata).sArr[3] = 3; //구조체 포인터로 구조체 내부 객체 접근
pdata->i = 1;
pdata->func();