포인터(Pointer)란?

변수나 배열의 요소, 함수 등이 놓여 있는 메모리의 주소를 값으로 갖는 변수. C 언어에서는 int *p처럼 선언하고, &x로 x의 주소를 꺼내며, *p로 p가 가리키는 곳의 값을 읽고 쓴다. 포인터에 정수를 더하면 가리키는 형의 크기를 단위로 하여 나아간다. 배열의 이름은 대부분의 식에서 첫 요소에 대한 포인터로 변환되며, a[i]는 *(a + i)와 같은 의미가 된다. 아무 곳도 가리키지 않음을 나타내는 값을 NULL 포인터라고 한다.

일본어 명칭: ポインタ

포인터(Pointer)은(는) C 언어 기초, 자료 구조와 알고리즘, ISA와 어셈블리 등 8개 분야의 연습 문제 25개에 나와요.

분야: C 언어 기초

선언과 기본 조작

int *p;는 'int를 가리키는 포인터' p의 선언입니다. p = &x;로 변수 x의 주소를 넣으면, *p = 5;는 x에 5를 씁니다. 함수의 인수는 값 전달이므로, 호출한 쪽의 변수를 함수 안에서 바꾸고 싶을 때는 그 주소를 포인터로 전달합니다. 구조체에 대한 포인터에서 멤버를 사용할 때는 (*p).m을 p->m으로 씁니다.

포인터 연산과 배열

int에 대한 포인터 p에 1을 더하면, 주소는 1바이트가 아니라 sizeof(int)바이트(대부분의 환경에서 4바이트) 나아갑니다. 배열의 이름은 sizeof나 &의 대상이 되는 경우 등을 제외하고 첫 요소에 대한 포인터로 변환됩니다. 따라서 함수의 인수로 배열을 전달하면 실제로는 첫 요소의 주소만 전달되며, 함수 안의 sizeof로는 배열 전체의 크기를 알 수 없습니다.

임베디드에서의 사용법과 주의점

마이컴에서는 주변 회로의 레지스터 주소를 (volatile uint32_t *)0x40020014처럼 정수에서 포인터로 변환하여 읽고 씁니다. 초기화하지 않은 포인터, NULL 포인터, 해제한 영역을 가리키는 댕글링 포인터를 통해 읽고 쓰는 것은 미정의 동작이며, OS가 없는 환경에서는 알아차리지 못한 채 다른 데이터를 망가뜨리기도 합니다.

도전해 보기: 포인터(Pointer) 관련 문제

배열과 비교했을 때 연결 리스트(각 원소가 다음 원소의 위치를 가리키는 포인터를 가진 리스트)의 특징으로 올바른 것은?

  1. A삽입할 위치 바로 앞의 원소를 알고 있으면 다른 원소를 옮기지 않고 포인터를 바꿔 다는 것만으로 삽입할 수 있지만, i번째 원소를 읽으려면 앞에서부터 차례로 따라가야 한다
  2. Bi번째 원소의 위치를 계산으로 구할 수 있으므로, 어느 원소든 일정한 시간 O(1)로 읽을 수 있다
  3. C원소가 메모리 위에 빈틈없이 연속해서 늘어서므로, 차례로 읽을 때 캐시나 프리페치가 잘 듣는다
정답과 해설 보기

정답: A. 삽입할 위치 바로 앞의 원소를 알고 있으면 다른 원소를 옮기지 않고 포인터를 바꿔 다는 것만으로 삽입할 수 있지만, i번째 원소를 읽으려면 앞에서부터 차례로 따라가야 한다

연결 리스트에서는 삽입이나 삭제할 위치를 알고 있으면 앞뒤의 포인터를 바꿔 쓰기만 하면 되므로 O(1)로 끝납니다. 한편 i번째 원소를 읽으려면 맨 앞에서부터 i번 포인터를 따라가야 하므로 O(n)이 걸립니다. 배열은 시작 주소와 원소의 크기로 i번째 주소를 계산할 수 있으므로 O(1)로 읽을 수 있지만, 중간에 삽입하려면 뒤쪽 원소를 밀어내는 O(n)의 수고가 듭니다. 또한 배열은 원소가 연속해서 늘어서므로 캐시나 프리페치가 잘 듣고, 메모리 위에 흩어지기 쉬운 연결 리스트는 같은 O(n)의 순회라도 훨씬 느려지는 경우가 있습니다.

int는 4바이트, 포인터는 8바이트로 한다. int형에 대한 포인터 p의 값(주소)이 0x1000일 때, p + 3의 값은?

  1. A0x100C
  2. B0x1012
  3. C0x1018
정답과 해설 보기

정답: A. 0x100C

포인터에 정수를 더하면 가리키는 형의 크기를 단위로 하여 나아갑니다. int는 4바이트이므로 p + 3은 3 × 4 = 12바이트 앞, 즉 0x1000 + 0xC = 0x100C를 가리킵니다. p + 3은 배열 요소 p[3]의 주소이며, *(p + 3)과 p[3]은 같은 의미입니다. char형에 대한 포인터라면 1바이트씩, double형(8바이트)에 대한 포인터라면 8바이트씩 나아갑니다.

자주 묻는 질문

포인터의 크기는 얼마인가요?

대부분의 64비트 환경에서는 8바이트, 32비트 마이컴에서는 4바이트입니다. 가리키는 곳의 형이 char든 double이든, 데이터에 대한 포인터의 크기는 일반적으로 같습니다. 포인터의 크기가 int의 크기와 같다고는 할 수 없으므로, 주소를 정수에 넣을 때는 stdint.h의 uintptr_t를 사용합니다.

const char *p와 char *const p는 무엇이 다른가요?

const char *p는 'const인 char를 가리키는 포인터'로, p를 통해 문자를 고쳐 쓸 수는 없지만 p 자체는 다른 주소로 바꿀 수 있습니다. char *const p는 '포인터 자체가 const'로, p는 바꿀 수 없지만 가리키는 곳은 고쳐 쓸 수 있습니다. const가 *의 왼쪽에 있으면 가리키는 곳, 오른쪽에 있으면 포인터 자체가 고정된다고 읽습니다.

해설 속 포인터(Pointer)

포인터(Pointer)이(가) 나오는 문제 (25)

  1. 배열과 비교했을 때 연결 리스트(각 원소가 다음 원소의 위치를 가리키는 포인터를 가진 리스트)의 특징으로 올바른 것은?
  2. int는 4바이트, 포인터는 8바이트로 한다. int형에 대한 포인터 p의 값(주소)이 0x1000일 때, p + 3의 값은?
  3. int는 4바이트, 포인터는 8바이트로 한다. 위의 프로그램을 실행했을 때의 출력으로 올바른 것은?
  4. 위의 함수 make가 반환하는 포인터에 대해 올바른 것은?
  5. C 언어에서 NULL 포인터를 참조했을(p가 NULL일 때 *p를 읽었을) 때의 설명으로 올바른 것은?
  6. RISC-V의 표준 호출 규약에서 정수 인자가 10개인 함수를 호출할 때 9번째와 10번째 인자는 어디로 전달되는가?
  7. ABI(애플리케이션 바이너리 인터페이스)에 대한 설명으로 올바른 것은?
  8. C 언어의 함수 포인터를 사용한 호출 '(*fp)(x)'는 RISC-V에서 일반적으로 어떤 명령어로 이루어지는가?
  9. 위의 함수에서 컴파일러가 *b의 읽기를 루프 밖으로 내보내는 최적화를 그대로는 수행할 수 없는 이유는?
  10. 컴파일러의 자동 벡터화에서 사용되는 명령의 종류로 올바른 것은 무엇인가?
  11. 프로그램이 OS가 할당하지 않은 주소에 접근했을 때 일반적으로 일어나는 일은?
  12. CAS를 사용한 락프리 자료 구조에서 문제가 되는 ABA 문제에 대한 설명으로 올바른 것은 무엇입니까?
  13. CUDA 유니파이드 메모리(통합 메모리)에 대한 설명으로 올바른 것은?
  14. 이진 힙을, 루트를 a[0]으로 하는 배열 a[0], a[1], a[2], …에 위쪽 단부터 차례로 왼쪽부터 채워 저장한다. a[4]의 자식과 부모로 올바른 것은?
  15. 원소 수가 많고 적재율을 0.5 정도로 유지한 해시 테이블에서, 선형 탐사의 개방 주소법이 체이닝(연결 리스트로 잇는 방법)보다 빨라지기 쉬운 이유로 가장 적절한 것은?

관련 용어

배열(Array) 변수(Variable) 미정의 동작(Undefined Behavior) 연결 리스트(Linked List) 스택(Stack) 페이지 폴트(page fault) 마이컴(마이크로컨트롤러) 프리페치(Prefetch) 인수(Argument) 동적 메모리 할당(Dynamic Memory Allocation)

용어집으로 돌아가기