
배열 포인터와 포인터 배열 차이는 C 언어에서 가장 헷갈리는 문법 중 하나입니다. 이름이 너무 비슷해서 비슷한 개념처럼 느껴지지만, 실제로는 메모리 구조와 사용 방식이 완전히 다릅니다.
이번 글에서는 이 주제를 선언 문법 암기로 풀지 않고, 무엇이 배열이고 무엇이 포인터인지부터 메모리 구조로 구분하는 방식으로 설명하겠습니다. 괄호 하나가 왜 선언 의미를 완전히 뒤집는지도 함께 보겠습니다.

선언 규칙의 공식 기준은 cppreference의 C pointer 설명을 참고할 수 있습니다. 다만 입문 단계에서는 우선순위 표보다 메모리 그림이 훨씬 도움이 됩니다.
배열 포인터는 무엇인가
배열 포인터는 배열 하나 전체를 가리키는 포인터입니다. 예를 들어 int (*p)[5]는 int 5개짜리 배열을 가리키는 포인터입니다.
즉 p 자체는 포인터 하나이고, 그 포인터가 가리키는 대상이 배열 전체라는 뜻입니다. 따라서 *p를 하면 배열 하나가 나오고, (*p)[i]처럼 접근합니다.
int arr[5] = {1,2,3,4,5};
int (*p)[5] = &arr;
printf("%d\n", (*p)[2]); // 3실전에서 포인터 배열이 자주 등장하는 대표 예시는 문자열 목록입니다. 예를 들어 여러 단어의 시작 주소를 보관할 때는 char *words[] 같은 형태가 자연스럽습니다. 각 칸이 서로 다른 문자열을 가리킬 수 있기 때문입니다.

포인터 배열은 무엇인가
포인터 배열은 말 그대로 포인터 여러 개를 원소로 가진 배열입니다. 예를 들어 int *arr[5]는 int를 가리키는 포인터 5개를 담는 배열입니다.
즉 arr 자체가 배열이고, 각 원소 arr[i]가 포인터입니다. 그래서 메모리 안에는 포인터가 여러 칸 들어 있는 구조가 됩니다.
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
printf("%d\n", *arr[1]); // 20
왜 괄호 하나가 이렇게 중요할까
선언을 읽을 때는 무조건 왼쪽에서 오른쪽으로만 읽지 말고, 변수 이름에서 시작해서 결합 우선순위를 따라가야 합니다. 이 습관이 없으면 비슷한 선언이 나올 때마다 다시 외워야 합니다.
C 선언은 우선순위가 있습니다. []가 *보다 더 강하게 결합하기 때문에, int *arr[5]는 먼저 arr[5]가 배열이라는 뜻이 되고, 그 배열 원소 타입이 int*가 됩니다. 반면 int (*p)[5]는 괄호가 먼저 p를 포인터로 묶어주기 때문에, p가 배열을 가리키는 포인터가 됩니다.
즉 괄호는 단순 장식이 아니라, 무엇이 먼저 결합되는지를 바꿔 선언 의미를 완전히 뒤집는 역할을 합니다.
초보자가 실제로 많이 틀리는 상황은 선언을 읽을 때입니다. int *arr[5]를 보고 배열을 가리키는 포인터라고 오해하거나, int (*p)[5]를 보고 포인터 여러 개가 들어 있는 배열처럼 느끼는 경우가 많습니다. 둘 다 비슷해 보이지만, 주어가 배열인지 포인터인지부터 다시 봐야 합니다.
입문자가 특히 헷갈리는 이유
- 이름이 너무 비슷하다
- 둘 다 포인터와 배열이라는 단어가 들어간다
- 선언을 왼쪽에서 오른쪽으로만 읽으려 하면 잘 안 보인다
- 메모리 구조를 안 보고 문법만 외우면 바로 꼬인다
그래서 이 주제는 문법 규칙보다 그림이 더 중요합니다. 배열 포인터는 포인터 1개가 배열 전체를 가리키고, 포인터 배열은 배열 칸마다 포인터가 들어 있다고 보면 훨씬 덜 헷갈립니다.
기본 포인터 감각은 포인터 입문 글, 층 구조 감각은 이중 포인터 글과도 이어집니다.



마무리
배열 포인터와 포인터 배열 차이는 이름이 아니라 구조를 봐야 잡힙니다. 하나는 포인터 하나가 배열 전체를 가리키는 구조이고, 다른 하나는 배열 원소들이 각각 포인터인 구조입니다.
즉 무엇이 배열이고 무엇이 포인터인지, 그리고 괄호가 결합 순서를 어떻게 바꾸는지만 분명히 잡으면 이 주제는 훨씬 덜 무섭게 보입니다.