C 언어 포인터와 다차원 배열 마스터하기
C 언어에서 배열은 연속된 메모리 위치에 저장된 동일 타입 값들의 모음입니다. 1차원이든 다차원이든 배열의 각 요소는 하나 이상의 고유한 정수 인덱스로 식별됩니다.
반면 포인터는 변수 주소를 저장합니다. 배열에서 0번째 요소 주소가 배열의 포인터입니다. 역참조 연산자를 사용해 포인터가 가리키는 값에 접근할 수 있습니다.
C에서 1차원, 2차원, 다차원 배열을 선언할 수 있습니다. 차원(dimension)이란 컬렉션에서 요소를 식별하는 데 필요한 인덱스 수를 말합니다.
포인터와 1차원 배열
1차원 배열에서 각 요소는 단일 정수로 식별됩니다.
int a[5] = {1, 2, 3, 4, 5};
여기서 숫자 "1"은 0번 인덱스, "2"는 인덱스 1, 이런 식입니다.
0번째 요소 주소를 저장하는 변수가 그 포인터입니다.
int *x = &a[0];
단순히 배열 이름도 0번째 요소 주소를 가리킵니다. 따라서 다음 표현식도 가능합니다.
int *x = a;
예제
포인터 값이 데이터 타입 크기만큼 증가하므로 x++는 포인터를 배열 다음 요소로 이동시킵니다.
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int length = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int *ptr = arr;
while (i < length) {
printf("arr[%d]: %d \n", i, *(ptr + i));
i++;
}
return 0;
}
출력
arr[0]: 1
arr[1]: 2
arr[2]: 3
arr[3]: 4
arr[4]: 5
포인터와 2차원 배열
1차원 배열이 요소 리스트라면, 2차원 배열은 표나 행렬 같습니다.
2차원 배열 요소들은 논리적으로 행과 열로 배치됩니다. 따라서 요소 위치는 행 번호와 열 번호 두 인덱스로 결정됩니다. 행과 열 인덱스 모두 0부터 시작.
int arr[2][2];
이런 배열은 다음과 같이 표현됩니다.
|
Col0 |
Col1 |
Col2 |
| Row0 |
arr[0][0] |
arr[0][1] |
arr[0][2] |
| Row1 |
arr[1][0] |
arr[1][1] |
arr[1][2] |
| Row2 |
arr[2][0] |
arr[2][1] |
arr[2][2] |
표 형태 배열은 논리적 표현일 뿐입니다. 컴파일러는 연속된 바이트 블록을 할당합니다. C에서 배열 할당은 행 우선(row-major) 방식으로, 요소들이 행 단위로 읽혀 들어갑니다.
여기서 3행 4열 2차원 배열(첫 대괄호 숫자는 항상 행 수)을 선언합니다.
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
컴파일러는 위 2차원 배열 메모리를 행 우선 순서로 할당합니다. 첫 요소 주소가 1000이고 int 크기가 4바이트라 가정하면, 배열 요소들 메모리 위치는 다음과 같습니다.
|
Row 0 |
Row 1 |
Row 2 |
| 값 |
1 2 3 4 |
5 6 7 8 |
9 10 11 12 |
| 주소 |
1000 1004 1008 1012 |
1016 1020 1024 1028 |
1032 1036 1040 1044 |
& 연산자로 배열 num 첫 요소 주소를 포인터 ptr에 할당합니다.
int *ptr = &arr[0][0];
예제 1
포인터가 1 증가하면 다음 주소로 이동합니다. 3x4 배열 모든 12개 요소를 루프로 접근할 수 있습니다.
#include <stdio.h>
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 포인터 ptr이 배열 num 가리킴
int *ptr = &arr[0][0];
int i, j, k = 0;
// 포인터 ptr로 배열 num 요소 출력
for (i = 0; i < 3; i++) {
for (j = 0; j < 4; j++) {
printf("%d ", *(ptr + k));
k++;
}
printf("\n");
}
return 0;
}
출력
1 2 3 4
5 6 7 8
9 10 11 12
일반적으로 배열 요소 주소는 다음 공식으로 구할 수 있습니다.
i행 j열 요소 주소 = baseAddress + [(i * 열개수 + j) * sizeof(array_type)]
우리의 3x4 배열에서,
arr[2][2] 주소 = 1000 + (2*4 + 2)*4 = 1044
위 그림 참조하면 arr[2][2] 주소가 1044임 확인 가능.
예제 2
역참조 포인터로 주소 값 가져오기. 이 공식으로 포인터 도움 받아 배열 순회.
#include <stdio.h>
int main() {
// 2차원 배열
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int ROWS = 3, COLS = 4;
int i, j;
// 포인터
int *ptr = &arr[0][0];
// 포인터 ptr로 배열 요소 출력
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLS; j++) {
printf("%4d ", *(ptr + (i * COLS + j)));
}
printf("\n");
}
return 0;
}
출력
1 2 3 4
5 6 7 8
9 10 11 12
포인터와 3차원 배열
3차원 배열은 2차원 배열들의 배열입니다. 세 첨자로 선언.
int arr[x][y][j];
이 배열은 "x" 개 층으로, 각 층은 "x" 행 "y" 열 테이블로 볼 수 있습니다.
3차원 배열 예제:
int arr[3][3][3] = {
{ {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
{ {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
{ {31, 32, 33}, {34, 35, 36}, {37, 38, 39} }
};
3차원 배열 포인터 선언:
int *ptr = &arr[0][0][0];
배열 이름이 0번째 요소 주소이므로, 3차원 배열 포인터를 이렇게 쓸 수도 있습니다.
int *ptr = arr;
"x" 행 "y" 열 각 층이 차지하는 바이트 수:
x * y * sizeof(data_type)
위 선언한 3차원 배열 arr 메모리 할당이 주소 1000부터 시작한다고 가정하면, 두 번째 층(i = 1)은 1000 + (3 * 3) * 4 = 1036 바이트 위치에서 시작.
ptr = 3차원 배열 arr 기준 주소
JMAX가 행 수, KMAX가 열 수면, 1번째 슬라이스 0행 0열 요소 주소는:
arr[1][0][0] = ptr + (1 * JMAX * KMAX)
i번째 슬라이스 j행 k열 요소 값 공식:
arr[i][j][k] = *(ptr + (i * JMAX * KMAX) + (j * KMAX + k))
예제: 포인터 역참조로 3차원 배열 출력
이 공식으로 포인터 역참조해 3차원 배열 출력.
#include <stdio.h>
int main() {
int i, j, k;
int arr[3][3][3] = {
{ {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
{ {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
{ {31, 32, 33}, {34, 35, 36}, {37, 38, 39} }
};
int JMAX = 3, KMAX = 3;
int *ptr = arr; // &arr[0][0][0];
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
for (k = 0; k < 3; k++) {
printf("%d ", *(ptr + (i * JMAX * KMAX) + (j * KMAX + k)));
}
printf("\n");
}
printf("\n");
}
return 0;
}
출력
11 12 13
14 15 16
17 18 19
21 22 23
24 25 26
27 28 29
31 32 33
34 35 36
37 38 39
일반적으로 포인터로 배열 접근은 첨자 표현으로 접근하는 것과 꽤 비슷합니다. 주요 차이는 첨자 배열 선언이 메모리를 정적으로 할당하는 반면, 포인터로는 동적 메모리 할당에 쓸 수 있다는 점입니다.
다차원 배열을 함수에 전달하려면 첨자 대신 포인터를 써야 합니다. 하지만 첨자 배열 사용이 포인터 사용보다 편리하며, 초보자에게 포인터는 어려울 수 있습니다.