이차원 포인터와 포인터 배열 및 배열 포인터
이차원 포인터, 포인터 배열, 배열 포인터 비교
이차원 포인터와 포인터 배열, 배열 포인터의 관계에 대한 포스팅을 진행하겠습니다.
이차원 포인터는 포인터의 포인터로 주소의 주소 값을 저장하는 역할을 합니다.
포인터 배열은 int *ptr[3]의 형식으로 연산자 우선 순위에 따라 배열이 먼저 선언되고 해당 배열 안의 값들이 int*인 포인터형인 배열입니다.
ptr+1은 4byte가 증가된 다음 배열 항목의 주소 값을 가리키게 됩니다.
배열 포인터는 int (*ptr)[3]의 형식으로 괄호에 의해 포인터가 먼저 선언되고 해당 포인터는 arr[][3]의 크기를 가지는 배열을 가리킬 수 있는 포인터입니다. ptr+1은 12byte가 증가된 다음 배열의 항목의 주소 값을 가리키게 됩니다.
포인터는 변수의 주소를 저장함으로써 저장된 주소 값을 기준으로 메모리에 할당된 배열에 대한 핸들링이 가능한 것 입니다.
주소 값을 통해 핸들링이 가능하다는 것이 이차원 포인터를 통해 이차원 배열을 핸들링 할 수 없는 이유입니다.
포인터 배열의 경우 배열에 포인터 자료형이 들어있는 형태로,
이차원 포인터를 통한 ptr, ptr+1, ptr+2의 주소는 포인터 배열의 다음 시작 주소와 정확하게 일치합니다.
하지만, 배열 포인터는 배열마다 int arr[2][3], int arr[3][4]의 크기로 선언되어 있을 경우 int (*ptr)[3], int (*ptr)[4]의 형태로 배열의 주소값을 대입해야 합니다.
전자의 경우, arr+1은 12byte가 증가하는 형태이고,
후자의 경우, arr+1은 16byte가 증가하는 형태이기 때문입니다.
배열 포인터도 다음 배열 요소의 시작 주소를 알아야 핸들링이 가능하다는 이야기입니다.
이차원 포인터의 경우도 마찬가지로 배열 포인트를 가리킬 때 강제형변환 시 시작 주소는 정확하게 일치하나,
배열의 열에 대한 크기를 알지 못하므로 다음 요소의 시작주소 값을 핸들링하지 못하고,
단순히 size(int*)의 크기만 증가시킨 부분을 포인팅하여 원하는 값이 나오지 않는 것을 볼 수 있습니다.
결국 이차원 포인터로 배열 포인터를 핸들링 할 수 없기 때문에 int **ptr = (*arr)[3]이라는 선언에 오류가 발생하는 것입니다.
예제 및 테스트 결과
아래에 테스트를 진행한 코드와 설명 첨부하도록 하겠습니다.
// Variables and Array declaration
int a=1, b=2, c=3;
int arr[2][3] = {{1,2,3}, {4,5,6}};
// Assigning a variable to an array of pointer
// 포인터 배열에 미리 선언한 변수의 주소 값을 넣는 과정입니다.
// pointerArr1는 포인터 배열로 3개의 배열 안에 각각 1차원 포인터가 들어있는 형태입니다.
int *pointerArr1[3] = {&a, &b, &c};
pointerArr1[0] = &a;
pointerArr1[1] = &b;
pointerArr1[2] = &c;
printf("%p:%d\n", pointerArr1[0], *pointerArr1[0]);
printf("%p:%d\n", pointerArr1[1], *pointerArr1[1]);
printf("%p:%d\n\n", pointerArr1[2], *pointerArr1[2]);
// Assigning an array of pointer to a two-dimensional pointer
// 포인터 배열의 주소를 이차원 포인터에 넣는 과정입니다.
// 대입 후 이차원 포인터는 포인터 배열의 시작 주소를 가리키게 됩니다.
int **doulePtr1 = NULL;
doulePtr1 = pointerArr1;
printf("%p:%d\n", doulePtr1[0], *doulePtr1[0]);
printf("%p:%d\n", doulePtr1[1], *doulePtr1[1]);
printf("%p:%d\n\n", doulePtr1[2], *doulePtr1[2]);
// Assigning an array to a one-dimensional pointer
// 선언한 2차원 배열을 일차원 포인터에 강제형변환을 하여 대입하였습니다.
// 배열은 2차원으로 선언되어 있지만 메모리에는 일차원의 형태로 존재하기 때문에 일차원 포인터를 통해 배열의 원소값에 접근이 가능합니다.
int *singlePtr = NULL;
singlePtr = (int*)arr;
printf("%p:%d\n", singlePtr, *singlePtr);
printf("%p:%d\n", singlePtr+1, *(singlePtr+1));
printf("%p:%d\n", singlePtr+2, *(singlePtr+2));
printf("%p:%d\n", singlePtr+3, *(singlePtr+3));
printf("%p:%d\n", singlePtr+4, *(singlePtr+4));
printf("%p:%d\n\n", singlePtr+5, *(singlePtr+5));
// Assigning an array to an array of pointer
// 선언한 2차원 배열을 포인터 배열이 가리키도록 하였습니다.
// 메모리에 배열은 1차원의 형태로 int형 6개가 존재합니다.
// 생성된 포인터 배열은 pointerArr2는 새로운 메모리 공간에 할당되고 각각 pointerArr2[0]은 arr[0]의 주소값, pointerArr2[1]은 arr[1]의 주소값을 가집니다.
int *pointerArr2[2];
pointerArr2[0] = arr[0];
pointerArr2[1] = arr[1];
printf("%p:%d\n", pointerArr2[0], *pointerArr2[0]);
printf("%p:%d\n\n", pointerArr2[1], *pointerArr2[1]);
// Assigning an array to an pointer of array
// 선언한 2차원 배열을 배열 포인터가 가리키도록 하였습니다.
// 배열 포인터는 int [][3]의 형태의 배열을 정확하게 가리킬 수 있습니다.
int (*arrPointer)[3];
arrPointer = arr;
printf("%p:%d\n", &arrPointer[0][0], arrPointer[0][0]);
printf("%p:%d\n", &arrPointer[0][1], arrPointer[0][1]);
printf("%p:%d\n", &arrPointer[0][2], arrPointer[0][2]);
printf("%p:%d\n", &arrPointer[1][0], arrPointer[1][0]);
printf("%p:%d\n", &arrPointer[1][1], arrPointer[1][1]);
printf("%p:%d\n\n", &arrPointer[1][2], arrPointer[1][2]);
// 컴파일 오류 발생
// 반대로, 포인터 배열은 원소 하나의 사이즈가 포인터의 사이즈 값을 가져 이차원 포인터로 가리킬 경우 이차원 포인터의 사이즈형과 같기 때문에 포인팅이 가능하나, 배열 포인터의 경우 배열의 행의 크기를 알 수 없으므로 이차원 포인터로 포인팅을 할 수 없습니다.
int **doulePtr2 = NULL;
doulePtr2 = arrPointer;
// 강제형변환 후 출력
// 배열 포인터가 제대로 핸들링되지 않습니다.
int **doulePtr2 = NULL;
doulePtr2 = (int**)arrPointer;
printf("%p:%d\n", &doulePtr2[0], doulePtr2[0]);
printf("%p:%d\n\n", &doulePtr2[1], doulePtr2[1]);