일반적으로 C 포인터로 큰 규모의 메모리를 다룰 때 메모리가 조각화되어 관리된다. 즉, 인접한 메모리를 할당하는 것이 아니라, 적당하게 힙 메모리에서 떨어진 구역의 메모리들을 가져와서 마치 하나로 연결되어 있고 인접되어 있듯이 사용하는 것이다. 


그렇다면 한 번에 인접한 메모리를 할당하려고 한다면 어떻게 해야 할까?

이 포스트에서, C언어로 다차원 배열을 할당하는 경우 메모리를 인접하게 할당하는 방법에 대해서 다룰 것이다.




| 일반적인 코딩 방법


우리는 일반적으로 다차원 배열을 동적으로 할당할 때, 다음과 같이 코딩을 한다.


 int rows = 2;
 int columns = 5;
 int i=0, j=0;

 int ** matrix = (int**) malloc(rows*sizeof(int*));

 for(i=0; i<rows; i++){
         matrix[i] = (int*)malloc(columns * sizeof(int));
 }


그런데, 이렇게 해버리면 다음과 같이 결과가 나타난다.




▲ [0][4] 원소와 [1][0] 원소 사이에 16바이트 갭이 생겼다.


첫 번째 행의 마지막 원소와 두 번째 행의 첫번쨰 원소의 주소가 int 변수의 크기인 4 byte만큼 차이가 나는 것이 아니라 16 byte가 차이난다. (위 숫자들은 전부 16진수임을 알기를 바란다. 40에서 50으로 갔음은 10바이트가 아니라 16바이트가 차이났음을 의미한다.)


이제, 해결책에 대해서 알아보자.


2차원 배열에 인접한 메모리를 할당하는 두 가지 접근 방법이 있다. 첫 번째 기법은 '바깥쪽' 배열을 먼저 할당한 후 전체 열에 대한 메모리를 할당하는 방법이고, 두 번째 기법은 모든 메모리를 한 번에 할당하는 방법이다. 






| 첫 번째 방법


첫 번째 기법을 다음 예제 코드에서 설명하고 있다. 첫 번째 malloc 함수 호출로 정수에 대한 포인터의 배열을 할당한다. 각 요소는 열에 대한 포인터를 가지게 된다. 두 번째 malloc 함수 호출은 우리가 선언할 다차원 배열의 제일 첫 번째 원소의 주소에 모든 요소에 대한 메모리를 할당하게 된다. (말이 어렵다면, 그냥 배열의 이름에다가 이후에 저장할 모든 메모리의 크기를 한 번에 할당한다고 생각하자.) 그리고 for 루프는 첫 번째 배열의 각 요소에 두 번째 malloc에서 할당한 메모리의 일부분을 지정한다.


int rows = 2;
int columns = 5;
int i=0, j=0;

int ** matrix = (int**)malloc(rows*sizeof(int*));
matrix[0] = (int*)malloc(rows*columns*sizeof(int));
for(i=1; i<rows; i++)
        matrix[i] = matrix[0] + i*columns;



엄밀히 따지면, 첫 번째 배열의 메모리는 배열의 '본체(body)' 부분과 떨어져 있을 수도 있다. 하지만 배열의 본체 부분에서는 메모리의 인접한 영역이 할당된다. 



▲ 배열의 모든 요소들이 인접하게 나열됬다.





| 두 번째 방법


다음 예제 코드에서는 두 번째 기법을 설명하고 있다. 


int * matrix = (int*)malloc(rows*columns*sizeof(int));


결과는 다음과 같다.



▲ 배열의 모든 요소들이 인접하게 나열됬다.


나중에 코드 안에서 이 배열을 참조할 때는 배열 첨자를 사용할 수 없다. 대신, 다음 코드에서 설명하는 것처럼 배열에 대한 인덱스를 수동으로 계산해야 한다. 각 배열 요소는 그 인덱스의 곱으로 초기화한다. (컴파일러가 배열 첨자를 허용할 때 필요한 배열의 형태에 대한 정보가 없으므로 배열 첨자를 사용할 수 없기 때문에 배열 첨자를 사용할 수 없는 것이다.)


int i=0, j=0;

for(i=0; i<rows;i++){
        for(j=0; j<columns; j++){
                *(matrix+(i*columns)+j) = i*j;
        }       
} 


2차원 배열에 대한 인접한 메모리를 할당하는 두 가지 일반적인 접근 방법에 대해 설명했다. 어떤 방법을 이용할 것인지는 어플리케이션에 따라 다르다. 하지만 두 번째 접근 방법이 '전체'배열에 대한 단일 메모리 블록을 생성한다.



+ Recent posts