지금까지 배열을 선언할 때는 int arr[10];처럼 크기를 미리 정해놓고 사용했습니다. 그런데 실제 프로그램을 만들다 보면 “사용자가 몇 개의 데이터를 입력할지 실행하기 전까지는 모른다”는 상황을 자주 만나게 됩니다. 이럴 때 배열 크기를 무작정 크게 잡아두면 메모리가 낭비되고, 너무 작게 잡으면 데이터가 넘칠 수 있습니다. 이 문제를 해결해주는 것이 바로 동적 메모리 할당입니다. 오늘은 malloc과 free를 중심으로 프로그램 실행 중에 메모리를 직접 할당하고 반납하는 방법을 알아보겠습니다.

동적 메모리 할당이 왜 필요한가

우리가 지금까지 써온 배열이나 변수는 컴파일할 때 크기가 이미 정해지는 “정적” 메모리입니다. 반면 동적 메모리는 프로그램이 실행되는 도중에, 필요한 만큼만 메모리를 요청해서 사용하는 방식입니다. C언어에서는 stdlib.h 헤더에 있는 malloc, calloc, realloc, free 함수를 이용해서 이 작업을 처리합니다. 그중에서도 가장 기본이 되는 것은 malloc(메모리를 할당)과 free(할당한 메모리를 반납)입니다.

malloc으로 메모리 할당하기

malloc 함수는 원하는 바이트 수만큼 메모리를 힙(heap) 영역에서 할당받고, 그 메모리의 시작 주소를 반환합니다. 만약 메모리를 할당하는 데 실패하면 NULL을 반환하므로, 반환값을 반드시 확인하는 습관을 들이는 것이 좋습니다.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    int *arr;

    printf("정수를 몇 개 입력하시겠습니까? ");
    scanf("%d", &n);

    arr = (int *)malloc(n * sizeof(int));

    if (arr == NULL) {
        printf("메모리 할당에 실패했습니다.\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * 10;
    }

    printf("입력받은 개수만큼 동적 배열을 채웠습니다.\n");
    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    free(arr);
    arr = NULL;

    return 0;
}

실행할 때 5를 입력했다고 가정하면 다음과 같은 결과가 출력됩니다.

정수를 몇 개 입력하시겠습니까? 5
입력받은 개수만큼 동적 배열을 채웠습니다.
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50

여기서 malloc(n * sizeof(int))는 정수 n개를 담을 수 있는 크기의 메모리를 요청한다는 뜻입니다. sizeof(int)를 곱해주는 이유는 malloc이 요구하는 인자가 “바이트 수”이기 때문입니다. 할당받은 메모리는 배열처럼 arr[i] 형태로 접근할 수 있고, 사용이 끝나면 free(arr)로 반드시 반납해야 합니다. 메모리를 반납한 뒤에는 arr = NULL;처럼 포인터를 비워주면, 실수로 이미 반납된 메모리에 다시 접근하는 사고를 줄일 수 있습니다.

calloc과 realloc도 함께 알아두기

malloc과 비슷하지만 조금씩 다르게 동작하는 함수들도 있습니다. calloc은 할당과 동시에 메모리를 0으로 초기화해주고, realloc은 이미 할당된 메모리의 크기를 나중에 늘리거나 줄일 때 사용합니다.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *arr = (int *)calloc(3, sizeof(int));

    printf("calloc 직후 값: %d %d %d\n", arr[0], arr[1], arr[2]);

    arr = (int *)realloc(arr, 5 * sizeof(int));
    arr[3] = 100;
    arr[4] = 200;

    printf("realloc 이후 값: %d %d %d %d %d\n",
           arr[0], arr[1], arr[2], arr[3], arr[4]);

    free(arr);

    return 0;
}

이 코드를 실행하면 다음과 같이 출력됩니다.

calloc 직후 값: 0 0 0
realloc 이후 값: 0 0 0 100 200

calloc(3, sizeof(int))는 정수 3개 크기의 메모리를 할당하면서 모든 값을 0으로 초기화합니다. 이후 realloc(arr, 5 * sizeof(int))를 호출하면 기존 메모리 내용은 그대로 유지한 채 크기만 5개로 늘어나고, 새로 늘어난 공간에 값을 채워서 사용할 수 있습니다.

코드 동작 설명 및 예상 실행 결과

  • 첫 번째 예제는 사용자가 입력한 개수만큼 malloc으로 동적 배열을 만들고, 값을 채운 뒤 출력하고 free로 메모리를 반납합니다.
  • malloc의 반환값이 NULL인지 확인하는 것은 실무에서도 매우 중요한 습관이며, 이를 생략하면 메모리 부족 상황에서 프로그램이 갑자기 죽을 수 있습니다.
  • 두 번째 예제는 calloc으로 초기화된 메모리를 할당한 뒤, realloc으로 크기를 늘려 기존 값은 유지하면서 새로운 값을 추가로 저장합니다.
  • 두 예제 모두 gcc 파일명.c -o 실행파일로 컴파일 후 실행하면 위와 같은 결과를 확인할 수 있습니다.

핵심 요약

  • 동적 메모리 할당은 프로그램 실행 중에 필요한 만큼 메모리를 힙에서 직접 요청하고 사용하는 방법입니다.
  • malloc(바이트 수)는 메모리를 할당하고 시작 주소를 반환하며, 실패하면 NULL을 반환하므로 항상 확인해야 합니다.
  • calloc은 할당과 동시에 메모리를 0으로 초기화하고, realloc은 이미 할당된 메모리의 크기를 조정할 때 사용합니다.
  • 동적으로 할당한 메모리는 사용이 끝나면 반드시 free로 반납해야 하며, 반납 후 포인터를 NULL로 초기화하면 실수를 줄일 수 있습니다.

다음 편에서는 프로그램이 종료된 뒤에도 데이터를 남겨두고 싶을 때 사용하는 “파일 입출력”을 주제로 다뤄보겠습니다.