1. 포인터 배열(Pointer Array)
포인터 배열은 모든 요소가 포인터로 구성된 배열을 의미합니다. 일반적인 선언 형태는 다음과 같습니다:
데이터형 *배열명[크기];배열 전체의 바이트 크기는 요소 개수와 포인터 크기의 곱으로 결정됩니다. 32비트 환경에서 포인터는 4바이트이므로, 포인터 2개를 담는 배열의 sizeof는 8이 됩니다.
포인터 배열의 대표적인 용도는 여러 개의 문자열을 효율적으로 관리하는 것입니다. 문자 포인터를 사용하면 2차원 배열보다 메모리를 절약하면서도 유연하게 문자열을 다룰 수 있습니다.
실습 예제: 여러 1차원 배열 접근
#include <stdio.h>
int main(void)
{
int row1[5] = { 10, 20, 30, 40, 50 };
int row2[5] = { 60, 70, 80, 90, 100 };
int row3[5] = { 110, 120, 130, 140, 150 };
int *ptr_set[3] = { row1, row2, row3 };
for (int idx = 0; idx < 3; idx++) {
for (int col = 0; col < 5; col++) {
printf("%d ", ptr_set[idx][col]);
}
printf("\n");
}
return 0;
}출력 결과:
10 20 30 40 50
60 70 80 90 100
110 120 130 140 150다양한 역참조 방식
2차원 배열과 포인터 배열을 조합하면 여러 가지 동등한 표현이 가능합니다:
#include <stdio.h>
int main(void)
{
int matrix[3][4] = {
{ 2, 4, 6, 8 },
{ 10, 12, 14, 16 },
{ 18, 20, 22, 24 }
};
int *p_arr[3];
for (int k = 0; k < 3; k++) {
p_arr[k] = matrix[k];
}
printf("동등한 접근 방식들:\n");
printf("%d %d %d %d\n", p_arr[1][1], *(p_arr[1] + 1),
*(*(p_arr + 1) + 1), (*(p_arr + 1))[1]);
return 0;
}모든 출력은 12입니다. 다음 표현들은 서로 동일합니다:
*(p_arr[i] + j)*(*(p_arr + i) + j)(*(p_arr + i))[j]p_arr[i][j]
문자열 배열로의 활용
#include <stdio.h>
int main(void)
{
char *words[] = {
"Hello",
"Pointer",
"Array"
};
for (int n = 0; n < 3; n++) {
printf("%s\n", words[n]);
}
return 0;
}중요한 점은 words 배열에 실제 문자열이 저장되는 것이 아니라, 각 문자열의 첫 번째 문자 주소가 저장된다는 것입니다.
포인터 배열 vs 2차원 문자 배열
char *word_ptrs[] = {"Algorithm", "Data", "Structure"};
char word_table[][10] = {"Algorithm", "Data", "Structure"};word_ptrs는 각 문자열의 길이에 상관없이 포인터만 저장하므로 메모리가 절약됩니다. 반면 word_table은 모든 행이 동일한 열 크기를 가지므로 낭비가 발생할 수 있습니다.
2. 배열 포인터(Array Pointer)
배열 포인터는 배열 전체를 가리키는 포인터입니다. 2차원 배열을 다룰 때 특히 유용합니다.
메모리 구조 이해
2차원 배열은 논리적으로는 행과 열로 구성되지만, 물리적으로는 연속된 메모리 공간에 저장됩니다:
int grid[3][4] = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};C 언어에서는 행 우선(row-major) 방식으로 배치되며, grid[0], grid[1], grid[2]는 각 행의 시작 주소를 의미합니다.
배열 포인터 선언
int (*row_ptr)[4] = grid;여기서 (*row_ptr)의 괄호는 필수입니다. int *row_ptr[4]로 작성하면 포인터 배열이 되기 때문입니다.
row_ptr가 가리키는 단위는 int[4]이므로, row_ptr + 1은 16바이트(4 × 4바이트)만큼 이동하여 다음 행을 가리킵니다.
배열 포인터로 2차원 배열 순회
#include <stdio.h>
int main(void)
{
int grid[3][4] = {
{ 100, 200, 300, 400 },
{ 500, 600, 700, 800 },
{ 900, 1000, 1100, 1200 }
};
int (*ap)[4] = grid;
printf("*(ap + 1)의 크기: %zu\n", sizeof(*(ap + 1)));
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 4; c++) {
printf("%4d ", *(*(ap + r) + c));
}
printf("\n");
}
return 0;
}출력:
*(ap + 1)의 크기: 16
100 200 300 400
500 600 700 800
900 1000 1100 1200*(ap + 1)의 크기가 16인 이유는, 이것이 한 행 전체를 나타내는 배열이기 때문입니다. 표현식 내에서 이는 해당 행의 첫 번째 요소 주소로 변환(배열-to-포인터 decay)됩니다.
동등 관계 정리
배열 포인터 ap와 2차원 배열 grid 사이에는 다음 관계가 성립합니다:
grid + i≡ap + i(i번째 행의 주소)grid[i]≡ap[i]≡*(grid + i)≡*(ap + i)(i번째 행의 첫 요소 주소)grid[i][j]≡ap[i][j]≡*(*(ap + i) + j)(i행 j열의 실제 값)
3. 핵심 비교: 포인터 배열 vs 배열 포인터
| 특성 | 포인터 배열 | 배열 포인터 |
|---|---|---|
| 선언 | int *p1[5]; | int (*p2)[5]; |
| 본질 | 배열 (요소: 포인터) | 포인터 (가리키는 대상: 배열) |
| 메모리 (32비트) | 20바이트 (5 × 4) | 4바이트 |
| 주요 용도 | 여러 문자열/배열 관리 | 2차원 배열 순회, 함수 인자 전달 |
괄호의 유무가 의미를 완전히 바꾼다는 점을 명심해야 합니다. []의 우선순위가 *보다 높으므로, int *p[5]는 int *(p[5])로 해석되어 5개의 포인터를 담는 배열이 됩니다.