Programming Language/C++

Ch06. 복합데이터 - 02. 다차원 배열

hustle_D 2025. 4. 1. 23:05
반응형

1. 다차원 배열

2차원 이상의 배열을 의미하며, 배열 안에 배열이 있는 구조라고 생각하면 된다.

가장 일반적인 형태는 2차원 배열로 수학시간에 배웠던 행렬과 같은 모습으로 구성된다고 생각하면 된다.

3 x 3 의 행렬

C++에서는 정적 배열로 표현되며, 배열의 차원 수가 많아질수록 메모리 접근이 복잡해진다.

 

2. 이차원 배열의 선언 

기본적인 이차원 배열의 선언은 

int arr[3][3];

과 같이 선언한다.

 

구체화 해보자면

타입 변수명[중첩되는 배열의 갯수][각 배열의 길이];

로 와 같이 표현 되며

int arr[2][3];

의 경우는 

int arr[3]
+
int arr[3]

의 형태로 구성된다.

2. 이차원 배열의 초기화

이차원 배열의 경우는 아래와 같이

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

배열 안에 배열을 배열의 갯수만큼 값을 넣어 선언을 하는 방법이 있고

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

이렇게 나란히 크기만큼 작성해서 초기화도 가능하다.

이게 가능한 이유는 기존의 배열을 확인해보는게 좋은데 배열의 경우 예를 들어 

int arr[3] = {1, 2, 3}

과 같은 배열이 존재한다고 한다면 이 배열은 메모리에서 

int - int - int

와 같이 연속된 위치에 존재하게 된다.

이게 2차원 배열이라고 

int - int - int 
int - int - int

와 같이 위치한게 아니라

int arr[2][3]

     👇
     
arr1[3] - arr2[3]

     👇
     
1.int - 1.int - 1.int - 2.int - 2.int - 2.int

와 같이 나열되어 있기 때문에 저렇게 초기화가 가능한 것이다.

 

3. 이차원 배열의 활용

1) 이차원 배열의 복사

이차원 배열의 경우도 그냥 배열과 동일하게 그대로 배열 이름으로는 복사할 수는 없다.

그렇기에 배열 처럼 for문을 돌려 배열을 복사하는 방법을 사용하는데 2차원 배열이기 때문에 배열을 하나 더 중첩해서 배열을 복사해줘야만 배열을 복사할 수가 있다.

 

동일하게 memcpy로도 복사가 가능하다

 

copy함수의 경우는 

이렇게 사용하면 에러를 발생하는데 그 이유는 2차원 배열의 경우 배열의 이름만 작성하면 메모리 구조처럼 쭉 늘어진 상태임을 인식하지를 모른다

그렇기에 명확하게 해당 위치가 어디부터 시작이고 어디까지 진행되는지를 알려주기 위해서는 진짜 0, 0 위치가 어디인지 부터 거기서 얼마만큼 떨어져 있다고 알려줘야 한다.

그래서 아래와 같이

이렇게 직접 arr1[0][0]의 주소값인 &arr1[0][0]를 전달하고 종료 위치를 &arr1[0][0] + 9를 전달해주고 작성될 위치또한 &arr2[0][0]임을 알려줘야지만 copy함수가 이해할 수 있다.

이는 copy 함수의 매개변수들이 1차원 반복자를 대상으로 사용되는 함수이기 때문이기도 하다.

 

2) 이차원 배열의 사용의 유의점

이차원 배열을 출력할때 for문 두개를 중첩해서 사용하게 된다.

예를 들어서 10000 * 10000의 크기를 가진 2차원 배열이 존재한다고 했을때

이렇게 선언되었다고 했을때는 보면 arr을 먼저 i로 크게 배열을 하나 호출 한 다음에 그 내부에서 하나 하나를 호출해서 더해준다.

이 경우 간단하게 크기가 3 x 3인 이차원 배열일때 

int - int - int | int - int - int | int - int - int

와 같이 메모리에 나열되어 있을때 접근을 순서대로 

1. int(i = 0, j = 0) - int - int | int - int - int | int - int - int
2. int - int(i = 0, j = 1) - int | int - int - int | int - int - int
3. int - int - int(i = 0, j = 2) | int - int - int | int - int - int
4. int - int - int | int(i = 1, j = 0) - int - int | int - int - int
5. int - int - int | int - int(i = 1, j = 1) - int | int - int - int
6. int - int - int | int - int - int(i = 1, j = 2) | int - int - int
7. int - int - int | int - int - int | int(i = 2, j = 0) - int - int
8. int - int - int | int - int - int | int - int(i = 2, j = 1) - int
9. int - int - int | int - int - int | int - int - int(i = 2, j = 2)

의 순서로 접근하게 된다.

근데 사실 여기서 램과 CPU에 대한 지식이 필요하다.

 

램에서 CPU로 데이터를 가지고 올때 속도가 느리기 때문에 캐시 메모리라는 것을 사용하는데 램에서 CPU로 뭔가를 가지고 왔을때 뭉텅이로 가져와서 이 캐시 메모리에 캐싱, 저장을 해두게 된다.

이게 통계적으로 로컬리티라는 지역성을 가지고 판단하는데 이게무슨말이냐면 시간 지역성이란게 있다 캐시엔 두가지 지역성이 존재하는데 먼저 한번 사용했던 메모리는 다시 사용할 가능성이 높다는 통계적인 근거를 가지고 만든 지역성(한 번 사용한 데이터는 다시 쓸 가능성 높음)이고, 공간 지역성의 경우는 사용했던 근처의 메모리를 사용할 가능성이 높다라는 통계적인 근거로 만든 지역성(사용한 메모리 근처도 곧 사용할 가능성 높음)인데 여기서 위 처럼 순서대로 메모리에 접근할때 그 근처의 데이터도 메모리로 가져오게 되고 이걸 캐싱에 저장해두기 때문에 순서대로 저렇게 접근할 경우 속도가 빠르게 접근이 가능하다.

 

그런데 만약 

이렇게 i와 j를 변경해서 접근하게 되면 

1. int(i = 0, j = 0) - int - int | int - int - int | int - int - int
2. int - int - int | int(i = 0, j = 1) - int - int | int - int - int
3. int - int - int | int - int - int | int(i = 0, j = 2) - int - int
4. int - int(i = 1, j = 0) - int | int - int - int | int - int - int
5. int - int - int | int - int(i = 1, j = 1) - int | int - int - int
6. int - int - int | int - int - int | int - int(i = 1, j = 2) - int
7. int - int - int(i = 2, j = 0) | int - int - int | int - int - int
8. int - int - int | int - int - int(i = 2, j = 1) | int - int - int
9. int - int - int | int - int - int | int - int - int(i = 2, j = 2)

이렇게 접근하면서 공간 지역성에 벗어나면서 메모리가 캐싱을 못하기 때문에 속도가 매우 느려진다.

 

그렇기 때문에 이 부분을 고려하면서 잘 사용해야만 한다.

반응형