모노산달로스의 행보
[C programming] 다차원 배열(2차원 배열)의 정의, 선언, 초기화, 주소와 값의 참조 본문
[C programming] 다차원 배열(2차원 배열)의 정의, 선언, 초기화, 주소와 값의 참조
모노산달로스 2024. 4. 4. 10:52C programming - 2차원 배열
리눅스 환경에서 네트워크 프로그래밍을 공부하기 위해서 C언어를 다시 복습해야 할 필요성을 느꼈습니다. 따라서 이번 기회에 배열부터 전처리기까지 내용들을 정리하겠습니다.
다차원 배열이란?
다차원 배열은 2차원 이상의 배열을 일컫는 용어입니다. 즉, 하나의 배열이 다른 배열을 가지고 있는 형태입니다. 처음 다차원 배열을 접하신다면 이해하기 힘든 개념으로 생각됩니다만, 이번 기회에 확실하게 정리하고 넘어가면 좋을 것 같습니다.
해당 포스트에서는 2차원 배열을 중심으로 살펴보겠습니다.
2차원 배열의 선언과 초기화
int array[4][3];
기존 배열과 선언 방법은 크게 다르지 않습니다. 자료형과 배열의 이름 그리고 배열의 크기를 선언합니다. 여기서 주목할 점은 배열의 크기 부분이 행(Row)과 열(Column)으로 나누어져 있습니다. 해당 내용을 예제 코드를 통해서 조금 더 자세히 알아보겠습니다.
#include <stdio.h>
int main(void)
{
int array[4][3];
array[0][0] = 1; array[0][1] = 2; array[0][2] = 3;
array[1][0] = 4; array[1][1] = 5; array[1][2] = 6;
array[2][0] = 7; array[2][1] = 8; array[2][2] = 9;
array[3][0] = 10; array[3][1] = 11; array[3][2] = 12;
printf("%2d %2d %2d \n",array[0][0], array[0][1], array[0][2]);
printf("%2d %2d %2d \n",array[1][0], array[1][1], array[1][2]);
printf("%2d %2d %2d \n",array[2][0], array[2][1], array[2][2]);
printf("%2d %2d %2d \n",array[3][0], array[3][1], array[3][2]);
return 0;
}
해당 코드는 2차원 배열을 선언한 다음 상수를 하나하나 초기화하는 방법을 사용하고 있습니다. 마치 좌표에 있는 요소를 찾아가는 것처럼 2차원 배열의 index가 사용되고 있습니다. 예를 들어 array[2][1]은 [2]번째 Row와 [1]번째 Column에 위치하는 요소인 8을 의미하는 것입니다.
이번에는 앞서 사용된 초기화 방법을 사용하지 않고 배열을 초기화하겠습니다. 2차원 배열 또한 1차원 배열과 마찬가지로 선언과 동시에 초기화할 수 있습니다.
#include <stdio.h>
int main(void)
{
int array[4][3] = {1, 2, 3, 4, 5};
printf("%2d %2d %2d \n",array[0][0], array[0][1], array[0][2]);
printf("%2d %2d %2d \n",array[1][0], array[1][1], array[1][2]);
printf("%2d %2d %2d \n",array[2][0], array[2][1], array[2][2]);
printf("%2d %2d %2d \n",array[3][0], array[3][1], array[3][2]);
return 0;
}
1차원 배열과 마찬가지로 배열이 index 순서대로 초기화되는 모습을 확인 가능합니다. 그리고 명시적으로 초기화하지 않은 부분은 0으로 초기화된 것 또한 확인할 수 있습니다.
그런데 위와 같은 방법을 2차원 배열에서는 행 단위로도 적용이 가능합니다.
#include <stdio.h>
int main(void)
{
int array[4][3] = {{1, 2}, {3}, {4}, {5, 6, 7}};
printf("%2d %2d %2d \n",array[0][0], array[0][1], array[0][2]);
printf("%2d %2d %2d \n",array[1][0], array[1][1], array[1][2]);
printf("%2d %2d %2d \n",array[2][0], array[2][1], array[2][2]);
printf("%2d %2d %2d \n",array[3][0], array[3][1], array[3][2]);
return 0;
}
2차원 배열을 초기화하는 중괄호 { } 내부에 새로운 중괄호 { }를 표기하여 행을 구분하는 것이 가능합니다. 새로운 괄호가 열릴 때마다 다음 행으로 넘어가는 모습입니다.
int array1[][] = {1, 2, 3, 4, 5, 6}; // error
int array2[3][3] = {1, 2, 3, 4, 5, 6}; // error
int array3[][3] = {1, 2, 3, 4, 5, 6};
int array4[][2] = {1, 2, 3, 4, 5, 6};
int array5[][1] = {1, 2, 3, 4, 5, 6};
마지막으로, 2차원 배열을 선언하는 경우 반드시 열의 길이(Column length)를 지정해야 합니다. 컴파일러가 열의 길이를 기반으로 행의 길이를 추정하는데, 그 이유는 C언어에서 다차원 배열을 메모리에 연속적으로 할당하기 때문입니다. 그렇다면 행의 길이(Row length)를 기반으로는 왜 열의 길이를 추정할 수 없을까요?
2차원 배열의 물리적 메모리 구조를 통해서 해답을 쉽게 얻을 수 있습니다.
위 그림을 통해 메모리에 연속적으로 값을 할당한다는 의미를 확인할 수 있습니다. 열의 길이만 주어진 경우 해당하는 길이만큼 값을 할당하고 다음 행으로 넘어가면 됩니다. 만약 행의 길이만 주어진 경우 컴파일러가 열의 길이를 추정할 방법이 없습니다.
2차원 배열의 주소 참조
#include <stdio.h>
int main(void)
{
int array[2][3]={1, 2, 3, 4, 5, 6};
printf("%x %x %x\n", &array[0][0], &array[0][1], &array[0][2]);
printf("%x %x %x\n", &array[1][0], &array[1][1], &array[2][2]);
return 0;
}
1차원 배열과 마찬가지로 2차원 배열의 주소는 &연산자를 통해 참조할 수 있습니다. 주소 값의 변화를 통해 메모리에 값이 연속적으로 할당되는 것을 다시 한번 확인 가능합니다.
2차원 배열의 주소 참조에는 두드러지는 세 가지 특징이 존재합니다.
2차원 배열의 이름은 2차원 배열의 시작 주소이다.
#include <stdio.h>
int main(void)
{
int array[2][2]={1, 2, 3, 4};
printf("%x %x\n", array, array+0);
printf("%x \n", array+1);
return 0;
}
2차원 배열의 이름으로 주소를 참조할 수 있습니다. 1차원 배열과 동일하지만 array+1의 주소를 출력하면 1행의 주소가 참조되는 것이 확인됩니다.
2차원 배열의 행의 요소는 행을 대표하는 주소이다.
#include <stdio.h>
int main(void)
{
int array[2][2]={1, 2, 3, 4};
printf("%x %x\n", array[0], &array[0][0]);
printf("%x %x \n", array[1], &array[1][0]);
return 0;
}
비슷한 예제로 2차원 배열의 행의 주소를 출력해 보았습니다. 그 결과 각 행의 첫 번째 열의 주소 값과 동일하게 출력되는 것이 확인됩니다.
2차원 배열에서 array[i] == *(array+i)는 주소이다.
#include <stdio.h>
int main(void)
{
int array[2][2]={1, 2, 3, 4};
printf("%x %x %x\n", array[0], *(array+0), *array);
printf("%x %x \n\n", array[1], *(array+1));
printf("%x %x \n", *(array+0)+0, *(array+0)+1);
printf("%x %x \n", *(array+1)+0, *(array+1)+1);
return 0;
}
*연산자는 기존에 값을 참조하기 위해 사용되었기 때문에 헷갈릴 수 있는 부분이 있습니다. 2차원 배열에서 *(array+i)와 같이 사용되는 경우 주소를 가리키게 됩니다.
2차원 배열의 값 참조
물론 2차원 배열에서 *연산자를 값의 참조에도 사용하는 것은 동일합니다.
#include <stdio.h>
int main(void)
{
int array[2][2]={10, 20, 30, 40};
printf("%d %d \n", *&array[0][0], *&array[0][1]);
printf("%d %d \n\n", *&array[1][0], *&array[1][1]);
printf("%d %d \n", *array[0]+0, *array[0]+1);
printf("%d %d \n\n", *array[1]+0, *array[1]+1);
printf("%d %d \n", **(array+0)+0, **(array+0)+1);
printf("%d %d \n", **(array+1)+0, **(array+1)+1);
return 0;
}
*&array[i][j] 와 같은 표현 방식은 정확하게 값을 출력하고 있습니다. &array[0][0]에 의해서 배열의 0번째 행의 0번째 열의 값의 주소가 반환됩니다. 해당 주소 값에 *연산자를 사용하면 주소가 가리키는 값을 참조하게 됩니다.
반면 *array[i]+j 와 같이 표현하면 엉뚱한 값이 출력됩니다. *연산자가 array[0]가 가리키는 주소의 값인 배열의 시작 주소를 반환합니다. 이후 상수가 더해지면서 주소 값의 변화가 아닌 정수형 값의 변화가 일어나게 됩니다.
**(array+i)+j 또한 위와 같은 흐름으로 출력이 진행됩니다. *(array+i)의 주소만을 먼저 값으로 반환했기 때문에 정수형 값 사이의 연산이 일어나게 됩니다.
위와 같은 상황을 해결해주기 위해서는 *(array[0] + 0) 혹은 *(*(array + 0) + 0)와 같이 소괄호를 한 번 더 사용해 주어야 합니다.
#include <stdio.h>
int main(void)
{
int array[2][2]={10, 20, 30, 40};
printf("%d %d \n", *&array[0][0], *&array[0][1]);
printf("%d %d \n\n", *&array[1][0], *&array[1][1]);
printf("%d %d \n", *(array[0]+0), *(array[0]+1));
printf("%d %d \n\n", *(array[1]+0), *(array[1]+1));
printf("%d %d \n", *(*(array+0)+0), *(*(array+0)+1));
printf("%d %d \n\n", *(*(array+1)+0), *(*(array+1)+1));
printf("%d %d \n", **((array+0)+0), **((array+0)+1)); // 잘못된 표현 방식
printf("%d %d \n", **((array+1)+0), **((array+1)+1)); // 잘못된 표현 방식
return 0;
}
소괄호를 통해서 출력하려는 값의 주소를 정확하게 참조할 수 있습니다. 가장 아래와 같은 표현 방식은 잘못된 결과를 출력하는데, 내부의 소괄호가 unnecessary 한 상태가 되어 **((array+0)+1)이 **(array+0+1)과 같이 표현되기 때문입니다.
이로서 배열에 관한 내용을 모두 정리했습니다. 다음으로는 C언어의 꽃인 포인터에 관한 내용으로 넘어가겠습니다.
'ProgrammingLanguage > C' 카테고리의 다른 글
[C programming] 표준 함수 (0) | 2024.04.16 |
---|---|
[C programming] 문자열을 가리키는 포인터, 포인터 변수 상수화 (0) | 2024.04.10 |
[C programming] 포인터를 통한 배열 값 접근, 배열 포인터와 포인터 배열 차이점, 값에 의한 호출과 참조에 의한 호출 차이점 (0) | 2024.04.10 |
[C programming] 포인터와 다차원 포인터 선언과 사용, 함수포인터, 주소 가감산법 (0) | 2024.04.09 |
[C programming] 1차원 배열의 정의, 선언, 초기화, 주소와 값의 참조 (0) | 2024.04.02 |