Skip to content
난이도 출제 빈출문제은행 6

C 배열과 포인터 ​

포인터(pointer)는 값 대신 메모리 주소를 담는 변수다. C는 배열·문자열·함수 인자를 모두 주소로 다루기 때문에, 포인터를 모르면 C 코드 출력 문제를 풀 수 없다. 시험은 *(p + 2)와 *p + 2의 차이, 배열 이름과 포인터의 관계, 이중 포인터, 문자열의 널 문자(null character)를 짧은 코드로 묻는다.

주소 연산자와 역참조 ​

&x는 변수 x의 주소를 구하는 주소 연산자(address-of operator)다. 포인터 p가 그 주소를 담고 있을 때 *p는 그 주소에 있는 값 자체를 가리키며, 이를 역참조(dereference)라 한다. 그래서 *p = 30은 p를 바꾸는 게 아니라 p가 가리키는 변수 x를 30으로 바꾼다. 포인터를 선언할 때 쓰는 int *p의 *는 "주소를 담는 변수"라는 표시일 뿐 역참조가 아니다.

배열과 포인터의 관계 ​

배열 이름은 식에서 쓰이면 첫 원소의 주소로 바뀐다. 그래서 int *p = a;가 가능하고, 다음 식들은 모두 같은 원소를 가리킨다.

표현뜻
a[i]i번째 원소
*(a + i)주소를 i칸 옮긴 뒤 역참조
p[i], *(p + i)p가 a를 가리키면 위와 같음

포인터에 1을 더하면 주소가 1바이트가 아니라 원소 하나 크기만큼 이동한다. int 배열이면 다음 int로 간다. 역참조 *가 +보다 연산자 우선순위(operator precedence)가 높으므로 *p + 2는 "첫 원소 값에 2를 더한 것"이고, *(p + 2)는 "세 번째 원소"다.

c
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
printf("%d %d %d\n", *(p + 2), *p + 2, p[4]);   // 30 12 50

배열을 선언할 때 크기를 비워 int a[] = {3, 1, 4};로 쓰면 초기값 개수인 3이 크기가 된다. 반대로 int a[5] = {3, 1};처럼 초기값이 모자라면 남은 원소는 0으로 채워진다. 쓰레기 값(garbage value)이 들어간다고 착각하기 쉬운 곳이다.

2차원 배열 b[2][3]에서 b + 1은 두 번째 행의 주소이므로 *(b + 1)은 그 행 자체(b[1])이고, 거기에 다시 + 2와 역참조를 적용한 *(*(b + 1) + 2)가 b[1][2]와 같다. 초기값은 행마다 중괄호로 묶어 쓰는 것이 기본이고, 묶지 않고 한 줄로 늘어놓아도 앞 행부터 차례로 채워진다.

이중 포인터 ​

이중 포인터(double pointer) int **pp는 포인터의 주소를 담는다. *pp는 pp가 가리키는 포인터 p 자체이고, **pp는 p가 가리키는 값이다. 따라서 *pp = &y는 p가 y를 가리키도록 바꾸고, **pp = 30은 p가 가리키는 변수의 값을 바꾼다. 함수 안에서 바깥의 포인터 변수를 바꿀 때 쓴다.

문자열과 널 문자 ​

C의 문자열은 char 배열 끝에 널 문자(null character) '\0'을 붙인 것이다. 이 표시가 있어야 printf("%s")나 strlen이 어디서 멈출지 안다. 따라서 char s[] = "HELLO";의 배열 크기는 글자 수 5에 널 문자 1을 더한 6이고, strlen(s)는 널 문자를 세지 않아 5다. s + 2를 %s로 출력하면 세 번째 글자부터 널 문자까지인 LLO가 나온다.

char *t = "HELLO";처럼 포인터에 문자열 상수를 담으면 배열을 새로 만드는 것이 아니라, 어딘가에 저장된 문자열의 첫 글자 주소를 t에 넣는다. t[i], *(t + i), t + i 표기는 배열과 똑같이 통한다. 다만 문자열 상수는 고칠 수 없는 저장 공간이라 t[0] = 'J'처럼 바꾸면 정의되지 않은 동작(undefined behavior)이 된다. 고쳐 쓸 문자열은 char s[]로 선언한다.

시험 포인트 ​

  • &는 주소, *는 그 주소의 값. *p + 1과 *(p + 1)을 구분한다.
  • a[i] == *(a + i). 포인터 +1은 원소 하나만큼 이동한다.
  • **pp는 값, *pp는 가리키는 포인터.
  • 문자열 배열 크기 = 글자 수 + 1(널 문자). strlen은 널 문자를 빼고 센다.
  • int a[5] = {3, 1};의 나머지 원소는 0. int a[] = {…}의 크기는 초기값 개수.

관련 문서