Chapter 18. 다차원 배열과 포인터의 관계
이번 차트는 사실상 포인터의 마지막 관문이라고 볼 수 있다.
그대신 그만큼 깊이 있는 이해를 요구하는 Chapter이기도 하다.
18-1 2차원 배열이름의 포인터 형
2차원 배열은 더블 포인터가 아니다. 2차원 배열이 무엇인지 어떻게 결정하는지가 이번 Chapter의 목표이다.
2차원 배열이름의 포인터 형을 결정짓는 방식은 1차원 배열이름의 포인터 형을 결정 짓는 방식과 다르다.
다른 이유:
1차원 배열은 type에 따라 메모리 접근방법 포인터 연산의 결과가 정해져서 tpye이 같으면 포인터 형이 같다.
그래서 1차원 배열은 자료형의 크기에 따라 포인터 연산을 진행하면 증감되는 크기를 알수 있으나, 2차원 배열은 자료형이 같아도 가로의 길이에 따라 R/W하는 값이 다르기 때문이다.
2차원 배열이름이 가르키는 것들은?
printf("%p", arr2d);
printf("%p", arr2d[0]);
이 두가지 모두 출력결과는 동일하다. 그러나 둘은 다르다.
이 둘 배열의 첫번째 요소의 주소 값을 가르키는건 동일하나,
arr2d는 arr2d 배열 전체를 뜻하고, arr2d[x]은 arr2d배열에서 x행만을 의미한다.
즉, sizeof 연산자를 사용하면 arr2d는 그 배열 전체의 크기가 반환되고, arr2d[x]은 x행의 크기만 반환된다.
배열이름 기반의 포인터 연산:배열이름에 1을 더한 결과는?
1차원 배열에서 배열이름에 +-x 를 하면 그 포인터의 자료형의 크기만큼 +-가 된다고 배웠다.
즉, 두 포인터의 포인터 형이 같다면, 두 포인터를 대상으로 하는 증감연산의 결과로 증감하는 값의 크기는 동일하다.
1차원 배열에서 배열이름의 포인터 형은 그 배열의 첫번째 요소의 자료형에 따라 결정된다.
ex) int* arr[3] == arr은 int형 더블포인터
그러나 2차원 배열에서 배열이름에 증감연산을 할 경우 "자료형 x 가로" 크기만큼 증감연산이 실행된다.
ex) int arr1[3][2] -> arr1+1 //이건 arr1+8 이랑 같다.
이처럼 2차원 배열에서 배열이름에 증감연산을 하면 증감한 만큼 열 이동이 된다.
arr1 == arr[0][0] == arr1[0], arr1 + 1 == arr1[1][0] == arr1[1], arr1+2 == arr1[2][0] == arr1[2]
같은 type형 2차원 배열이더라도 이처럼 가로의 크기에 따라 포인터 형이 달라진다.
2차원 배열에서는 포인터 연산시 행이동이 아닌 열이동 (세로의 이동)이 일어나기 때문에 가로의 길이랑 자료형의 길이를 곱해서 그 수만큼 증가 시켜줘야 하기 때문에 2차원 배열이름의 포인터 형은 '가로길이'와 '요소(type)' 두개가 필요하다. 따라서 이 두가지 정보를 담을수 있는 포인터형 선언방법이 별도로 필요하다.
세로의 길이는 달라도 포인트 형이 같을수 있다. 왜냐하면 세로길이는 포인터 연산 결과에 영향을 미치지 않기 때문이다.
즉, 이 두가지로 2차원 배열이름의 포인터 형이 결정된다.
1. 가리키는 대상은 무엇인가?
2. 배열이름(포인터)를 대상으로 값을 1 증감 시 실제로는 얼마가 증감하는가?
int arr[3][4] 배열이름의 포인터 형을 묻는다면,
"arr이 가르키는 대상은 int형 변수이고, 포인터 연산 시(arr대상 증감연산) sizeof(int) x 4의 크기단위로 값이 증감하는 포인터 형이다." 라고 답할수 있다.
이러한 유형(int arr[3][4])의 포인터 변수 선언 방법:
int (*ptr) [4]; 라고 선언한다.
ptr: ptr은 포인터!
int: int형 변수를 가리키는 포인터! //가르키는 대상의 자료형
[4]: 포인터 연산 시 4칸을 건너뛰는 포인터!
즉, ptr앞에 * 선언은 ptr이 포인터 선언이 되게 하고, [4]는 포인터 연산 시 4칸씩 건너뛰며, ptr이 가르키는 변수가 int형 변수이기 때문에 int형 변수를 4칸씩 건너뛴다는 의미이다.
2차원 배열을 가르키는 용도로만 사용되고 이러한 유형의 포인터 변수를 가리켜 "배열 포인터 변수" 라 한다.
18-2 2차원 배열이름의 특성과 주의사항
배열 포인터와 포인터 배열은 다르다.
배열 포인터는 2차원 배열을 가르키는 포인터 변수이고, 포인터 배열은 포인터 변수로 이뤄진 배열이다.
이 둘이 헷갈린다면 P.382 쪽을 참고하자.
2차원 배열을 함수의 인자로 전달하기
int arr[2][7];
double arr2[4][5];
SimpleFunc(arr1, arr2);
void SimpleFunc(int (*parr1)[7], int (*parr2)[5])
이런 식으로 가능하다. 그리고 "int (*parr1)[7] == int parr1[][7], int (*parr2)[5] == int parr2[][5]" 같은 의미이며
이 둘은 1차원 배열때와 마찬가지로 매개변수의 선언에서만 같은 의미를 지닌다.
2차원 배열을 매개변수로 전달할때는 세로의 길이도 같이 전달해줘야 한다. (sizeof(arr1) / sizeof(arr1[0]))
전체길이에 가로의 크기(한 행의 크기)를 나누면 세로의 길이를 알 수 있다.
2차원 배열에서도 arr[i]와 *(arr+i)는 같다. (arr이 배열의 이름이거나 포인터 변수의 이름일땐 항상 같다.)
int arr[3][2] = { {1, 2}, {3, 4}, {5, 6} };
이 배열에서 6이 저장된 인덱스 [2][1]의 위치의 값을 4로 변경시키기 위해서는 다음 이 네가지 식이 있다.
arr[2][1] = 4;
(*(arr+2))[1] = 4;
*(arr[2]+1) = 4;
*(*(arr+2)+1) = 4;
**arr == *(*(arr+0)+0)
arr[2][1] = 4
- 여기서 [2][1]은 한 문장이 아니다. 그래서 arr[2] 먼저 계산후 뒤에 [1]을 계산하기 때문에 (arr[2])[1]로 표현이 가능하다.
---------------------------------------------------------------------------------------------------------------
1차원 배열의 포인터 형 결정방법:
-배열이름이 가르키는 대상이 무엇인지?
2차원 배열의 포인터 형 결정방법:
-배열이름이 가르키는 대상이 무엇인지?
-포인터 연산의 결과(가로길이 x type)
함수의 인자로 2차원배열을 받을때는 세로의 길이도 인자로 줘야한다.
sizeof(arr1) / sizeof(arr[0]) 전체 크기에 한 행의 크기를 나눠주면 세로의 길이가 나온다.