C언어 포인터에서 가장 헷갈리는 두 녀석이다.
핵심은 연산자 괄호와 우선순위다. 괄호는 묶음을 표시하며, [](배열)이 *(포인터)보다 우선순위가 높다는 것으로 해석하면 된다.
1. 포인터 배열 (Array of Pointers)
“나는 배열이다. 그런데 내용물이 주소(Pointer)”
int *arr[3]; // = int* arr[3]; // *가 자료형에 붙어서 표기하는 것이 정식표기 방법이며 이러면 자료형을 읽기 편하다.
- 해석: int*이 작동한다. 즉, 인티저형 포인터란 의미. 그리고
arr[3]은 3개짜리 배열이다. 그러므로 안에 들어가는 것은int*(주소)이다. - 구조: 포인터 변수 3개가 나란히 생성된다. 각각의 칸에 서로 다른 주소를 담을 수 있다.
- 용도: 문자열 배열(
char* str[]), 흩어져 있는 변수들을 묶어서 관리할 때, 행의 길이가 다른 2차원 배열(Jagged Array)을 만들 때 사용한다.
2. 배열 포인터 (Pointer to an Array)
“나는 포인터다. 그런데 가리키는 대상이 배열(Array)”
int (*arr)[3]; // int* (arr)[3]이 될 수 없다. *은 괄호로 묶여있기 때문
- 해석:
()로 묶여있어 *가 밖으로 나올 수 없다.*arr $\rightarrow$arr은 포인터다. $\rightarrow$ 무엇을 가리키나?int형 변수 3개가 모인 덩어리(배열)를 가리킨다. - 구조: 포인터 변수는 딱 1개만 생성된다. 이 포인터는 $int \times 3$ 크기의 메모리 블록을 통째로 가리킨다.
- 용도: 2차원 배열을 함수의 인자로 넘길 때, 2차원 배열의 행 단위 이동이 필요할 때 사용한다.
3. 코드 실험 및 결론
작성한 코드를 통해 확인한 핵심 차이는 “초기화와 접근성”이다.
Case A: 포인터 배열
int *ptrArr[2]; // 각 칸마다 주소를 개별적으로 넣어줘야 함 (귀찮음) ptrArr[0] = matrix[0]; ptrArr[1] = matrix[1];
- 특징: 행(Row)들이 메모리상에 연속적이지 않아도 연결할 수 있다. 유연하지만 초기화가 번거롭다.
Case B: 배열 포인터
int (*arrPointer)[3] = matrix; // 한 번에 2차원 배열 전체를 가리킬 수 있음 (편리함)
- 특징:
matrix자체가 메모리에 연속적으로 배치된 2차원 배열이므로, 타입만 맞으면 한 번에 연결된다.arrPointer + 1을 하면int3개 크기(12바이트)만큼 점프하여 다음 행을 가리킨다.
요약
| 구분 | 선언 | 정체 | sizeof (32bit 기준) | 주 용도 |
| 포인터 배열 | int *p[3] | 배열 (주소 저장소) | $4 \times 3 = 12$ byte | 문자열 배열, 비연속 메모리 관리 |
| 배열 포인터 | int (*p)[3] | 포인터 (배열 지시자) | $4 \times 1 = 4$ byte | 2차원 배열 처리, 함수 인자 전달 |
결론:
함수에서 2차원 배열을 통째로 넘겨받아 행과 열을 누비며 작업해야 한다면, 배열 포인터(int (*p)[col])를 쓰는 것이 정석이다. 반면, 여러 개의 문자열이나 흩어진 변수들을 묶어서 관리하고 싶다면 포인터 배열이 답이다.