Programming/C

C언어 - 다차원 배열과 포인터의 관계

JunsuKim 2021. 10. 4.
728x90

1차원 배열이름의 포인터 형과 2차원 배열이름의 포인터 형

int arr[10];

여기서 arr은 int형 포인터이다.

이를 함수의 인자로 전달하기 위해서는 다음과 같다.

#include<stdio.h>

void SimpleFunc(int *ptr) {
    . . . .
}

int main() {
    . . . .
    SimpleFunc(arr);
    . . . .
}

이제 2차원 배열을 봐보자.

int arr2[2][3];

1차원 배열에서 arr은 int형 포인터였으니 2차원 배열인 arr2는 int형 더블 포인터일까?

이 질문의 답은 "아니다"이다.

int형 더블 포인터이기 위해서는 다음과 같아야 한다.

int *ptr[2];

2차원 배열 이름이 더블 포인터를 가리킨다고 오해하면 안 되는 것이다.

그럼 2차원 배열 이름의 포인터 형은 무엇일까? 이를 위해 몇 가지를 더 알아보도록 하겠다.

2차원 배열 이름이 가리키는 것 

int arr2[2][3];

배열이름 arr2가 가리키는 것은 무엇일까? 인덱스 기준으로 [0][0]에 위치한 첫 번째 요소를 가리킨다.

그렇다면 arr2[0], arr2[1], arr2[2]는 각각 무엇을 가리킬까?

1행, 2행, 3행의 첫 번째 요소를 가리킨다.

그럼 첫번째 요소의 주소 값을 출력하기 위한 방법을 작성해보자.

printf("%p", arr2);
printf("%p", arr2[0]);

이 두 문장의 출력 결과는 같다. 그럼 두 문장은 같은 것일까?

예제를 봐보자.

#include<stdio.h>

int main() {
    int arr2[3][3];
    printf("%d\n", arr2);
    printf("%d\n", arr2[0]);
    printf("%d\n", arr2[0][0]);
    
    printf("%d\n", arr2[1]);
    printf("%d\n", arr2[1][0]);
    
    printf("%d\n", arr2[2]);
    printf("%d\n", arr2[2][0]);
    
    printf("sizeof(arr2): %d\n", sizeof(arr2));
    printf("sizeof(arr2[0]): %d\n", sizeof(arr2[0]));
    printf("sizeof(arr2[1]): %d\n", sizeof(arr2[1]));
    printf("sizeof(arr2[2]): %d\n", sizeof(arr2[2]));
    return 0;
}

위 7개의 출력 결과를 보면 각 행의 첫 값들이 동일하게 출력되는 것을 볼 수 있다.

이제 밑에 4개의 출력 결과를 보자.

arr2에 sizeof() 함수를 사용했더니 배열 전체의 크기를 반환하였고, 나머지 arr2[0], arr2[1], aarr2[2]의 경우, 각 행의 크기만을 반환했다. 

결론적으로 arr2와 arr2[0]을 같지 않다는 것을 알 수 있다.

배열이름 기반의 포인터 연산

int arr[3];
double arr2[3];

이 두 배열에 각각 1을 더한 연산을 하면 어떤 값이 나올까?

배열이름 arr은 int형 포인터이므로 arr + sizeof(int)의 결과가 나오고,

arr2는 double형 포인터이므로 arr2 + sizeof(double)의 결과가 나올 것이다.

결론적으로 "두 포인터의 포인터 형이 같다면, 두 포인터를 대상으로 하는 증가 및 감소 연산의 결과로 증감하는 값의 크기는 같다."

그렇다면 2차원 배열을 통해 증감 연산을 해보면 어떻게 될까?

다음 예제를 보자.

#include<stdio.h>

int main() {
    int arr1[3][2];
    int arr2[2][3];
    
    printf("arr1: %p\n", arr1);
    printf("arr1 + 1: %p\n", arr1 + 1);
    printf("arr1 + 2: %p\n\n", arr1 + 2);
    
    printf("arr2: %p\n", arr2);
    printf("arr2 + 1: %p\n", arr2 + 1);
    printf("arr2 + 2: %p\n", arr2 + 2);
    return 0;
}

16진수 형태라 바로 보이진 않겠지만, arr1은 값을 1 증가시킬 때마다 8씩 늘어나고, arr2는 12씩 증가한다.

그림을 보면 이해가 더 쉬울 것이다.

이와 같이 2차원 배열의 포인터 형은 자료형의 동일하더라도 가로의 길이에 따라서 달라진다.

2차원 배열이름의 포인터 형의 결론

2차원 배열이름의 포인터 형에는 두 가지 정보가 담겨야 한다.

  • 가리키는 대상이 무엇인가?
  • 배열이름을 대상으로 값을 1 증감 시 실제로 얼마가 증감하는가?
int arr[3][4];

위 배열의 포인터 형은 결국 무엇인가?

이는 "배열이름 arr이 가리키는 대상은 int형 변수이고, 포인터 연산 시  실제로 (sizeof(int) * 4(가로의 길이))의 크기만큼 증감하는 포인터 형이다."라고 해야 한다. 

아쉽지만 2차원 배열뿐만 아니라 그 이상의 차원을 가지는 배열에서는 1차원 배열과 달리 int형 포인터, double형 포인터와 같이 딱 떨어지는 표현이 존재하지 않는다. 

2차원 배열 이름의 특성과 주의사항

1. '배열 포인터'와 '포인터 배열' 구분

int *A[4]; // 포인터 배열
int (*B)[4]; // 배열 포인터

이 둘의 차이는 소괄호의 유무이다. 그러나 A는 int형 포인터 변수로 이루어진 배열이고, B는 가로길이가 4인 int형 2차원 배열을 가리키는 용도의 포인터 변수이다. 

 

2. 2차원 배열을 함수의 인자로 전달

2차원 배열의 포인터 형을 결정할 수 있으니, 2차원 배열의 주소 값을 인자로 전달받는 함수를 정의할 수 있다.

int main() {
    int arr1[2][3];
    double arr2[4][5];
    SimpleFunc(arr1, arr2);
    . . . .
}

이와 같은 함수 원형이 있다.

arr1과 arr2의 주소 값을 전달받을 수 있는 매개변수 p1, p2라 하고 SimpleFunc함수를 선언해 보자.

void SimpleFunc(int (*p1)[3], double (*p2)[5]) {
    . . . .
}

우리는 각각 가로 길이가 3과 5인 배열의 주소 값을 전달받을 것이기 때문에 배열 포인터를 사용해야 한다.

다른 방법으로도 함수를 정의할 수 있다.

void SimpleFunc(int p1[][3], double p2[][5]) {
    . . . .
}

int (*p1)[3]과 int p1[][3], double (*p2)[5]와 double p2[][5] 이 두 매개변수 선언은 동일한 선언이기 때문에 두 가지 방법으로 정의가 가능하다. 하지만 둘은 매개변수의 선언에서만 같은 의미이고, 그 이외의 영역으로까지 확대해서 동일한 선언으로 간주하면 안 된다.

 

728x90

댓글