본문 바로가기

공부/C언어

(C 예제) 다차원 배열과 포인터

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

// 1차원 배열 int형 포인터
int arr[10];

arr과 &arr[0]은 똑같이 그 배열의 첫 주소다.

각 요소마다 숫자가 있다.

// 1차원 배열 int형 이중 포인터
int * parr[20];

parr라는 배열도 포인터이고 각 요소들의 포인터도 있으므로 이중 포인터이다.

 

// 2차원 배열의 포인터
int arr2d[3][4];

 

3층 짜리 배열인 arr2d에 int형이 각 층에 4개씩 있다.


2차원 배열이름이 가리키는게 뭘까?

//예시
int arr2d[3][3];

배열이름 arr2d은 인덱스 기준으로 [0][0]에 위치한

첫 번째 요소의 주소와 1행 전체의 주소를 동시에 의미한다.

그래서

&arr2d[0][0] 이랑 arr2d[0]과도 동일한 의미를 가진다.

 

결론적으로 arr2d, arr2d[0], &arr2d[0][0] 중에서 무엇을 쓰든 첫번째 주소를 가리킨다.

 

3차원 배열도 똑같다.

 

전체 배열의 주소와(a)

각 높이의 첫번째 주소와(a[0])

각 높이의 첫번째의 첫번째 층 주소와(a[0][0])

각 높이의 첫번째의 첫번째 층과 첫번째 요소의 주소(&a[0][0][0])

모두 첫번째 주소를 가리킨다.


그렇다면 arr2d와 arr2d[0]은 같은 것인가?

같은건 아니다.

 

1차원배열의 경우

arr2d는 배열의 주소

arr2d[0]은 0번째 요소에 있는 숫자값이 된다.

 

2차원배열의 경우에는 같다.

arr2d는 배열의 주소

arr2d[0]은 0번째 층의 주소이다.

숫자값은 arr2d[0][0]이다.

arr2d[0][0]앞에 &를 붙이면 숫자값이 아니라 주소로 된다.

 

아래의 예를 보자.

sizeof를 할 경우에는 다르게 나온다.

arr2d는 배열 전체의 크기를 반환하고

arr2d[0], arr2d[1], arr2d[2]는 각 행의 크기를 반환한다.


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

int는 4씩 double은 8씩 증가한다.


2차원 배열이름 대상의 포인터 연산

앞전에 n + k는 &n[k]와 같다고 했다.

 

여기서는 2차원 배열이기에 arr1 + 1과 arr1[1]이 같다. &는 안붙여도된다.

arr1은 arr1[0]과 같고 &arr1[0][0]과도 같다.

 

arr1[1]도 arr1[1][0]과 같다.

 

arr1은 행이 2개이므로 개당 4바이트씩 해서 arr1[0]과 arr1[1]은 8이나 차이가 나고,

결과가 저렇게 나온것이다. 2차원 배열이라서 이런 결과가 나올 수 있는 것이다.

 

arr2도 똑같다. 행이 3개 이므로 개당 4바이트씩 해서 arr2[0]과 arr2[1]이 12나 차이가 난다.

 

그러므로 arr1은 int가 2개씩 커지는 포인터(int * [2])이고,

arr2는 int가 3개씩 커지는 포인터(int * [3])이다.

 

이렇듯 2차원 배열이름의 포인터 형은 배열의 가로길이에 따라서도 나뉜다.

이러한 특징 때문에 2차원 배열이름의 포인터 형 결정이 햇갈린다.

 

3차원 배열도 비슷하다.

+ 1을 하면 2차원 배열은 행에 따라서 나뉘지만,

3차원은 행과 열에 따라서 나뉜다.


결론

1. 2차원 배열이름의 포인터 형을 결정짓는 두 가지 요소

  • 가리키는 대상은 무엇인가?
  • 배열이름(포인터)를 대상으로 값을 1 증감 시 실제로는 얼마나 증감하는가?

2. 왜 다른가?

  • 1차원 배열이름의 자료형은 1차원 배열이름이 가리키는 대상만으로 결정이 되는데
    2차원 배열이름의 자료형은 그렇지 않은 이유는?

3. 포인터 형을 통한 메모리의 접근과 주소 값의 증가

  • 1차원 배열의 경우에는 배열이름이 가리키는 대상을 기준으로 메모리의 접근방법과
    포인터 연산시의 증감의 크기가 결정되었다.
    그러나 2차원 배열에서는 위의 두 가지 정보가 모두 존재해야 이 둘을 결정할 수 있다.

int arr[3][4]의 포인터 형은?

  • 가리키는 대상: int형 변수
  • 포인터 연산의 결과: sizeof(int)x4의 크기단위로 값이 증감

                  ↓
이러한 유형의 포인터 변수 ptr의 선언

int (* ptr) [4];

int (* ptr) [4] → ptr은 포인터

int (* ptr) [4] → int형 변수를 가리키는 포인터

int (* ptr) [4] → 포인터 연산 시 4칸 건너뛰는 포인터


2차원 배열이름의 포인터 형 결정짓는 연습

// char형
char (*arr1)[4];
// double형
double (*arr2)[7];

arr1은 char형 변수를 가리키면서, 포인터 연산시 sizeof(char)x4의 크기단위로

값이 증감하는 포인터 변수이다.

 

arr2는 double형 변수를 가리키면서, 포인터 연산시 sizeof(double)x7의 크기단위로

값이 증감하는 포인터 변수이다.

 

그러면 역으로 가보자.

int형 변수를 가리키면서, 포인터 연산시 sizeof(int)x2의 크기단위로

값이 증감하는 포인터 변수 ptr1은 뭘까?

 

float형 변수를 가리키면서, 포인터 연산시 sizeof(flaot)x5의 크기단위로

값이 증감하는 포인터 변수 ptr2는 뭘까?

 

답은

// int형
int (*ptr1)[2];
// float형
float (*ptr2)[5];

이다.

 

2차원 배열이름의 포인터 관련 예제이다.

// 2차원 배열이름의 포인터 관련 예제
#include <stdio.h>

void 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 }
	};

	int(*ptr)[2];
	int i;

	ptr = arr1;
	printf("** Show 2,2 arr1 **\n");
	for (i = 0; i < 2; i++)
		printf("%d %d \n", ptr[i][0], ptr[i][0]);
	ptr = arr2;
	printf("** Show 3,2 arr2 **\n");
	for (i = 0; i < 3; i++)
		printf("%d %d\n", ptr[i][0], ptr[i][1]);
	ptr = arr3;
	printf("** Show 4,2 arr3 **\n");
	for (i = 0; i < 4; i++)
		printf("%d %d \n", ptr[i][0], ptr[i][1]);
} 

arr1, arr2, arr3는 다 int형 2차원 배열이고 가로 길이가 같으므로

포인터 형이 동일하다.

그래서 int (*ptr)[2];라고 선언했다.

 

타입이 똑같으면 배열 이름으로 포인터를 지정해서 그 포인터로

배열에 접근할 수 있다.

배열 포인터와 포인터 배열

// 포인터 배열
int * whoA [4];
// 배열 포인터
int (*whoB) [4];

 

포인터 배열은 포인터 변수로 이루어진 변수이고,

배열 포인터는 배열을 가리킬 수 있는 포인터 변수이다.


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

1차원 배열에서는 

f(배열이름, 배열의 순서);로 호출하고

void f(int *n, int b)로 받거나,

void f(int n[], int b)로 받았다. 그중에서 후자가 더 알아보기 쉬워서

자주 쓰인다고 했다.

 

2차원 배열은

// 호출
void main(void)
{
	int arr1[2][7];
	double arr2[4][5];
	SimpleFunc(arr1, arr2);
	...
} 

호출은 위처럼 하고

 

// 함수
void SimpleFunc(int(*parr1)[7], double(*parr2)[5])
{
	...
}
// 이렇게 하거나
// 함수
void SimpleFunc(int parr1[][7], double parr2[][5])
{
	...
}
// 이렇게 해야됨

함수는 위처럼 해야된다.

보통 후자 쪽이 알아보기 쉬워서 선호한다.

parr1[][7]에서 7부분에는 무조건 숫자를 넣어줘야한다.

parr1의 type이 달라지기때문이다.

 

parr2도 마찬가지로 5부분에는 맞는 숫자를 넣어줘야한다.

 

전자 쪽은 후자 쪽의 비어있는 부분이 생략되면서 *를 넣고 괄호가 생긴다.

괄호가 없으면 포인터 배열이 되기 때문에 주의해야한다.


2차원 배열을 함수의 인자로 전달하는 예제

// 예제
#include <stdio.h>

void ShowArr2DStyle(int(*arr)[4], int column) // int arr[][4]도 가능
{
	int i, j;
	for (i = 0; i < column; i++)
	{
		for (j = 0; j < 4; j++)
			printf("%d ", arr[i][j]);
		printf("\n");
	}
	printf("\n");
}

int Sum2DArr(int arr[][4], int column)
{
	int i, j, sum = 0;
	for (i = 0; i < column; i++)
	{
		for (j = 0; j < 4; j++)
			sum += arr[i][j];
	}
	return sum;
}

void 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])); //arr1배열의 세로길이 계산식
	ShowArr2DStyle(arr2, sizeof(arr2) / sizeof(arr2[0])); //arr2배열의 세로길이 계산식
	printf("arr1의 합: %d\n", Sum2DArr(arr1, sizeof(arr1) / sizeof(arr1[0])));
	printf("arr2의 합: %d\n", Sum2DArr(arr2, sizeof(arr2) / sizeof(arr2[0])));
}


2차원 배열에서도 arr[i]와 *(arr+i)는 같다.

 

int arr[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; 에서 

arr[2][1] = 4;

(*(arr+2))[1] = 4;

*(arr[2]+1) = 4;

*(*(arr+2)+1) = 4;
는 모두 같은 표현이다.