포인터(Pointer)

포인터의 개념

컴퓨터에게 CPU와 Memory는 핵심 요소이고, 모든 메모리는 주소를 갖는다. 포인터는 그 메모리의 주소를 가리키는 변수를 의미한다. 보통 그 주소에는 다른 변수가 저장되어 있는데, 포인터를 이용하면 해당 데이터에 접근할 수 있다.

보통 컴퓨터의 메모리는 바이트 단위로 구성되어 있고 각 바이트마다 순차적으로 주소가 매겨져 있다.

char ch = 'a'; // 1. 문자형 변수 ch 선언 및 초기화
char* p; // 2. 포인터 변수 p 선언
p = &ch; // 3. 포인터 변수 값 저장
*p = 'b'; // 4. ch = 'b'; 와 동일. p가 가리키는 곳의 내용을 'b'로 교체
char** pp; // 5. 이중 포인터 변수 pp를 선언
pp = &p; // 6. p의 주소를 pp에 복사
  1. 이 문장은 char형 변수 ch를 선언하고 초기화 한다. 즉, 메모리에서 char 형을 저장할 수 있는 크기의 공간을 찾아 ch란 이름을 부여하고, 그 공간에 ‘a’를 복사한다. 모든 메모리는 주소를 가지므로 변수 ch도 주소가 있는데, 아래 그림의 예에서는 0x1236번지가 할당되었다. 모든 변수(또는 객체)는 반드시 메모리를 차지하며 주소를 갖지만 상수는 공간을 차지하지도 않고 따라서 주소도 없다.

  2. char* p; 문장은 새로운 포인터 변수 p를 선언한다. 포인터 역시 변수이기 때문에 메모리를 차지하고 주소를 갖는다. 아래 그림의 예에서는 0x5678번지가 이 변수를 위해 할당되었다. 자료형이 char* 이므로 p는 char 변수가 저장되어 있는 공간의 주소를 저장하기 위해 사용될 것이다. 그렇다면 포인터 변수의 크기는 어떻게 될까? 일단은 단순히 4바이트라고 생각하자. 사실 최근에 개인용 컴퓨터의 메모리가 4G바이트(2 바이트)를 넘어서면서 64비트 운영체제를 사용하는 경우가 많다. 만약 64비트 주소체계를 사용한다면 포인터 변수의 크기는 8바이트가 된다.

    32

  3. p = &ch; 문장은 ch의 주소를 포인터 변수 p에 저장하는 문장이다. 변수의 주소는 &연산자를 이용하여 얻는다. 즉 &ch 연산의 결과는 주소(변수 ch가 있는 메모리의 주소)이며, 자료형은 ch가 char형이므로 &ch는 char*가 된다. 아래 그림에서는 &ch의 주소가 0x1236번지이므로 p에는 이 주소가 복사된다. 결국 포인터 변수 p에 변수 ch의 주소가 저장되어 있으며, 이것을 보통 ‘p가 변수 ch를 가리킨다’고 한다.

  4. 포인터 변수가 가리키는 메모리의 내용을 추출하거나 변경하려면 *연산자를 이용한다. 앞의 문장들에 이어 *p = ‘b’; 라는 문장을 실행 시키면 변수 ch의 값이 바뀌게 된다.

  5. char** pp; 문장은 이중 포인터 변수 pp를 선언한다. pp도 변수이므로 메모리 공간을 차지하고 주소를 갖는다. pp의 크기는 p와 동일한데 이는 하나의 컴퓨터에서 주소체계는 동일하며 주소가 몇 번지이든 주소를 저장하는데 필요한 공간은 동일하기 때문이다.

  6. pp = &p; 문장은 변수 p의 주소를 이중 포인터 변수 pp에 복사한다. 3에서 설명한 바와 같이 &p는 변수 p의 주소를 추출한다. 그렇다면 &p의 자료형은 무엇일까? 답은 char** 이다. p의 자료형이 char* 이므로 &p는 char* 형 변수가 들어 있는 메모리의 주소이므로 char**가 된다. 결국 &p의 자료형은 변수 pp의 자료형과 일치하며 &p의 연산 결과를 pp에 복사할 수 있다. 이 문장으로 *pp와 p는 전적으로 동일해졌다.

Untitled

Untitled

포인터 변수를 선언할 때 *는 어느 쪽에 붙여도 상관 없지만, 의미적으로는 자료형 뒤에 붙이는 것이 좋다. 다만 한 문장에서 여러 포인터 변수를 선언하고자 할 때는 변수 앞에 *를 붙여야 한다.

// 아래 2개는 동일하다.
char* p;
char *p;

char* p, q, r; // p는 char* 변수가 되지만, q, r은 char 변수가 된다.
char *p, *q, *r;

포인터는 다음과 같이 여러 가지 자료형의 대상에 대해 선언될 수 있다.

int* pi; // int 변수의 주소를 저장하기 위한 포인터
float* pf; // float 변수의 주소를 저장하기 위한 포인터
char* pc; // char 변수의 주소를 저장하기 위한 포인터
int** pp; // 포인터 변수의 주소를 저장하기 위한 포인터
Test* ps; // Test 객체의 주소를 저장하기 위한 포인터
void (*f)(int); // int를 매개변수로 갖고 반환이 없는 함수의 주소를 저장하기 위한 포인터
void* p; // 임의 자료형의 주소를 저장하기 위한 포인터
pi = (int*)p; // p를 정수 포인터로 변경하여 pi로 대입

void *p는 아무것도 가리키지 않는 포인터를 의미한다. void 포인터는 필요할 때마다 다른 포인터로 바꾸어서 사용한다.

함수와 포인터

포인터는 함수의 매개변수나 반환형으로 사용될 수 있다. 특정한 변수를 가리키는 포인터가 함수의 매개변수로 전달되면 그 포인터를 이용하여 호출된 함수에서 원래의 변수를 변경할 수 있다.