C언어 - (15) : 다차원 배열의 포인터
다차원 배열과 포인터
1차원 배열이 선언된 경우, 배열의 이름이 포인터의 역할을 하고 첫번째 메모리 주소를 가리킨다고 했었다.(기억 안나면)
2차원 배열도 동일한 원리를 지니는데 `int arr2d[3][3];`의 2차원 배열이 선언되었다고 할 때,
`arr2d`라는 배열의 이름은 `arr2d`가 갖는 메모리 주소의 가장 첫번째 주소를 가리키는 포인터의 역할을 하게 된다.
추가적인 것은, 2차원의 경우에 첫번째 차원의 index값이 각 행이 갖는 메모리 주소의 첫번째 주소를 가리키는 포인터 역할을 하게 된다.
쉽게 이해하면 2차원 배열 안의 `arr2d[0], arr2d[1], arr2d[2]`라는 1차원 배열이 존재하고, 이러한 1차원 배열들은 기존 1차원 배열이 갖는 특성을 갖게 되는 것이다.
$\rightarrow$ (arr2d == arr2d[0] == &arr2d[0][0])
2차원 배열의 포인터 연산(메모리 연산) / 포인터 형
1차원 배열과 마찬가지로 2차원 배열의 포인터 연산시에도 배열의 포인터 형에 따라, 연산 값이 상이하게 나타난다.
1차원 배열의 포인터 형의 경우에는 `int arr[3];`이나 `int arr[10];`이나 `int *`의 형을 가지게 되므로 '+1'의 포인터 연산을 해주었을때
4만큼 메모리 포인터 값이 증가한다.
2차원 배열은 1차원과 비슷하지만 열의 크기(두번째 차원 배열의 크기)에 따라 같은 `int`형이더라도 다른 포인터 형을 갖게 된다.
예를 들어, `int arr[3][2]`는 열의 크기가 2이므로 +1의 포인터 연산시 2*4 = 8만큼 포인터 값이 증가하지만,
`int arr[2][3]`은 열의 크기가 3이므로 연산시 3*4 = 12만큼 포인터 값이 증가한다.
#include <stdio.h>
int main(void)
{
int arr[3];
double arr2[3];
printf("%p %p \n", arr, arr+1); // 4씩 증가
printf("%p %p \n", arr2, arr2+1); // 8씩 증가
// 두번째 차원의 배열 크기 * 자료형(int) 만큼 증가
int arr3[3][2];
printf("%p %p %p \n", arr3, arr3+1, arr3+2); //2*4 = 8씩 증가
int arr4[2][3];
printf("%p %p\n", arr4, arr4+1); //3*4 = 12씩 증가
return 0;
}
다소 번잡스럽지만, 2차원 이상의 배열에 대한 포인터 형은 '가리키는 대상'과 '포인터 연산의 결과'를 종합해서 정의하도록 한다.
예를 들어 `double arr[3][4]`의 포인터 형은
- 가리키는 대상 : `double`
- 포인터 연산의 결과(+1)
- `4(건너뛰는 배열의 크기)
- 2차원 배열의 경우에는 두번째 차원의 크기 / 3차원 배열은 두번째*세번째 차원의 크기`
- `4(건너뛰는 배열의 크기)
이에 따라, 2차원 배열을 가리키는 포인터 변수는 다음과 같이 선언할 수 있다.
`double arr[3][4]`에 대한 포인터 변수는
가리키는 대상 (*포인터 변수 이름) [포인터 연산의 결과] $\rightarrow$ `double (*ptr) [4]`로 선언할 수 있다.
#include <stdio.h>
int main(void)
{
int arr1[2][2]={{1,2},{3,4}};
int arr2[3][2] = {{1,2},{3,4},{5,6}};
int arr3[4][2] = {{1,2},{3,4},{5,6},{7,8}};
// arr1, arr2, arr3 모두 포인터 형이 같음
int (*ptr)[2];
// arr1
ptr = arr1;
printf("** Show 2,2 arr1 **\n");
for (int i=0; i<2; i++)
printf("%d %d \n", ptr[i][0], ptr[i][1]);
// arr2
ptr = arr2;
printf("** Show 2,2 arr1 **\n");
for (int i=0; i<3; i++)
printf("%d %d \n", ptr[i][0], ptr[i][1]);
// arr3
ptr = arr3;
printf("** Show 2,2 arr1 **\n");
for (int i=0; i<4; i++)
printf("%d %d \n", ptr[i][0], ptr[i][1]);
return 0;
}
위의 예시 코드에서 확인할 수 있듯이 세가지 2차원 배열(arr1, arr2, arr3)의 포인터 형이 전부 `int (*ptr) [2]`로 같기 때문에,
하나의 포인터 변수 `ptr`만으로 위와 같이 세가지 2차원 배열을 접근, 표현할 수 있게 된다.
배열 포인터와 포인터 배열의 차이
- 포인터 배열
- 포인터 변수를 안에 담는 배열
- `int * ptr [4]`
- 배열 포인터
- 배열을 가리킬 수 있는 포인터 변수
- `int (*ptr) [4]`
2차원 배열의 함수 인자 전달
이전에 1차원 배열을 함수의 인자로 전달해 사용했던 방식과 유사하게, 2차원 배열 역시 함수의 인자로 전달해 사용 가능하다.
간단히 복습해보자면, 1차원 배열의 경우에는 (1) 배열의 이름을 포인터 변수로 지정해 인자로 전달하거나 (2) 배열 형태의 인자로 전달하거나 두가지의 방법이 있었다.
`(1)void function(int * ptr) | (2)void function(int arr[])`
2차원 배열도 유사한 두가지의 방법이 존재한다.
- 배열 포인터의 형태로 인자 전달
- `void function(int (*parr)[7])`
- 2차원 배열의 형태로 인자 전달
- `void function(int arr2d[][7])`
두가지 방법 모두 동일한 결과를 도출한다
조금 더 자세한 예시는 다음과 같다.
#include <stdio.h>
void ShowArr2DStyle(int (*arr)[4], int column) // 배열 포인터 형태 인자
{
for (int i=0; i<column; i++)
{
for (int j=0; j<4; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
}
int Sum2DArr(int arr[][4], int column) // 2차원 배열 형태 인자
{
int sum=0;
for (int i=0; i<column; i++)
{
for (int j=0; j<4; j++)
sum+=arr[i][j];
}
return sum;
}
int main(void)
{
int arr1[2][4]={1,2,3,4,5,6,7,8};
int arr2[3][4]={1,1,1,1,3,3,3,3,5,5,5,5};
ShowArr2DStyle(arr1, sizeof(arr1)/sizeof(arr1[0]));
ShowArr2DStyle(arr2, sizeof(arr2)/sizeof(arr2[0]));
printf("arr1의 함: %d \n", Sum2DArr(arr1, sizeof(arr1)/sizeof(arr1[0])));
printf("arr2의 함: %d \n", Sum2DArr(arr2, sizeof(arr2)/sizeof(arr2[0])));
return 0;
}
참고
1차원 배열 `arr`에서 `arr[i] = *(arr+i)`인 특징이 있었는데, 2차원 배열도 마찬가지이다.
`int arr[3][2]`인 배열이 있다고 할때,
- arr[2][1] = 4
- (*(arr+2))[1] = 4
- *(arr[2]+1) = 4
- *(*(arr+2)+1) = 4
모두 다 같은 의미이다.