38.3 포인터에 할당된 메모리를 2차원 배열처럼 사용하기

이번에는 포인터에 메모리를 할당하여 세로 크기 3, 가로 크기 4인 2차원 배열처럼 사용해보겠습니다.다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

  • 자료형 **포인터이름 = malloc(sizeof(자료형 *) * 세로크기);와 같이 세로 공간 메모리 할당
    • 반복문으로 반복하면서 포인터[i] = malloc(sizeof(자료형) * 가로크기);와 같이 가로 공간 메모리 할당
    • 반복문으로 반복하면서 free(포인터[i]);와 같이 가로 공간 메모리 해제
    • free(포인터);와 같이 세로 공간 메모리 해제

pointer_like_two_dimensional_array.c

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

int main()
{
    int **m = malloc(sizeof(int *) * 3);   // 이중 포인터에 (int 포인터 크기 * 세로 크기)만큼
                                           // 동적 메모리 할당. 배열의 세로

    for (int i = 0; i < 3; i++)            // 세로 크기만큼 반복
    {
        m[i] = malloc(sizeof(int) * 4);    // (int 크기 * 가로 크기)만큼 동적 메모리 할당.
                                           // 배열의 가로
    }

    m[0][0] = 1;    // 세로 인덱스 0, 가로 인덱스 0인 요소에 값 할당
    m[2][0] = 5;    // 세로 인덱스 2, 가로 인덱스 0인 요소에 값 할당
    m[2][3] = 2;    // 세로 인덱스 2, 가로 인덱스 3인 요소에 값 할당

    printf("%d\n", m[0][0]);    // 1: 세로 인덱스 0, 가로 인덱스 0인 요소의 값 출력
    printf("%d\n", m[2][0]);    // 5: 세로 인덱스 2, 가로 인덱스 0인 요소의 값 출력
    printf("%d\n", m[2][3]);    // 2: 세로 인덱스 2, 가로 인덱스 3인 요소의 값 출력

    for (int i = 0; i < 3; i++)    // 세로 크기만큼 반복
    {
        free(m[i]);                // 2차원 배열의 가로 공간 메모리 해제
    }

    free(m);    // 2차원 배열의 세로 공간 메모리 해제

    return 0;
}

실행 결과

1
5
2

먼저 다음과 같이 이중 포인터에 2차원 배열의 세로 공간에 해당하는 메모리를 할당합니다. 이때 세로 공간에는 값이 들어가지 않고 가로 공간의 메모리 주소가 들어갑니다. 따라서 sizeof(int)가 아닌 sizeof(int *)처럼 포인터의 크기를 구한 뒤 세로 크기 3을 곱해줍니다(32비트에서는 intint *의 크기가 4바이트라 큰 차이가 없지만 64비트에서는 int의 크기가 4바이트, int *의 크기가 8바이트이므로 포인터가 들어갈 공간이라는 것을 확실하게 구분해야 합니다).

int **m = malloc(sizeof(int *) * 3);    // 이중 포인터에 (int 포인터 크기 * 세로 크기)만큼
                                        // 메모리 할당. 배열의 세로

이중 포인터에 2차원 배열의 세로 공간에 해당하는 메모리를 할당하는 모습을 그림으로 표현하면 다음과 같이 됩니다.

그림 38‑2 이중 포인터에 배열의 세로 공간 할당

이제 세로 크기만큼 반복하면서 2차원 배열의 가로 공간에 해당하는 메모리를 할당합니다. 가로 공간에는 int형 숫자가 들어갈 것이므로 sizeof(int)에 가로 크기 4를 곱해줍니다.

for (int i = 0; i < 3; i++)            // 세로 크기만큼 반복
{
    m[i] = malloc(sizeof(int) * 4);    // (int 크기 * 가로 크기)만큼 동적 메모리 할당.
                                       // 배열의 가로
}

이중 포인터에 2차원 배열의 가로 공간에 해당하는 메모리를 할당하는 모습을 그림으로 표현하면 다음과 같이 됩니다.

그림 38‑3 이중 포인터에 배열의 가로 공간 할당

즉, m은 pointer to pointer to int이므로 int **m으로 선언하고 m[0], m[1], m[2]는 pointer to int이므로 int *가 들어갑니다(메모리 할당). 마지막으로 m[0][0], m[0][1] 등은 int만 들어갑니다.

이제 2차원 배열을 사용하듯이 [ ] [ ]에 세로 인덱스, 가로 인덱스를 지정하여 값을 할당하거나 가져올 수 있습니다.

  • 포인터[세로인덱스][가로인덱스]
m[0][0] = 1;    // 세로 인덱스 0, 가로 인덱스 0인 요소에 값 할당
m[2][0] = 5;    // 세로 인덱스 2, 가로 인덱스 0인 요소에 값 할당
m[2][3] = 2;    // 세로 인덱스 2, 가로 인덱스 3인 요소에 값 할당

printf("%d\n", m[0][0]);    // 1: 세로 인덱스 0, 가로 인덱스 0인 요소의 값 출력
printf("%d\n", m[2][0]);    // 5: 세로 인덱스 2, 가로 인덱스 0인 요소의 값 출력
printf("%d\n", m[2][3]);    // 2: 세로 인덱스 2, 가로 인덱스 3인 요소의 값 출력

포인터를 다 사용했다면 먼저 가로 공간에 해당하는 메모리부터 해제합니다. 그러고 나서 세로 공간에 해당하는 메모리를 해제합니다. free(m);처럼 세로 공간에 해당하는 메모리를 먼저 해제해버리면 가로 공간 메모리를 해제할 수 없으므로 주의합니다.

for (int i = 0; i < 3; i++)    // 세로 크기만큼 반복
{
    free(m[i]);                // 2차원 배열의 가로 공간 메모리 해제
}

free(m);    // 2차원 배열의 세로 공간 메모리 해제

즉, 메모리를 할당할 때 세로 → 가로 순서로 할당했으므로 해제할 때는 반대로 가로 → 세로 순서로 해제합니다.