본문 바로가기
카테고리 없음

C언어 - 동적 메모리 할당

by kik328288 2026. 5. 5.

C언어 malloc (free, 동적, 실기)

지금까지 다룬 변수와 배열은 모두 컴파일 시점에 크기가 결정되는 정적 할당 방식이었다. 그러나 실제 프로그램은 실행 도중에야 비로소 필요한 데이터의 크기를 알 수 있는 경우가 훨씬 많다. 사용자가 입력할 학생 수가 미리 정해져 있지 않거나, 파일을 읽어 들이고 나서야 그 크기를 알게 되는 상황이 대표적이다. 이러한 문제를 해결하기 위해 C언어는 실행 시점에 메모리를 요청하고 해제할 수 있는 동적 메모리 할당(Dynamic Memory Allocation) 기능을 제공한다(출처: cppreference — Memory management). 정보처리기사 실기에서도 malloc과 free의 활용, 그리고 캐스팅과 sizeof를 결합한 코드의 출력 결과를 묻는 문제가 자주 출제된다. 본 글은 동적 메모리의 개념과 네 가지 핵심 함수, 그리고 실기 빈출 패턴을 코드 예시와 함께 정리한다. 제가 학교 C 프로젝트에서 가장 자주 만난 사고가 free를 빠뜨려 발생한 메모리 누수였는데, 동아리 멘토가 "malloc 한 줄을 적는 순간 그에 짝이 되는 free도 같은 호흡으로 적는다"는 한 줄을 알려준 후로는 누수 사고가 거의 사라졌다.

 

동적 메모리 할당의 개념과 malloc

C언어 프로그램의 메모리 공간은 크게 코드 영역, 데이터 영역, 스택(Stack), 힙(Heap)으로 구분된다. 일반적인 지역 변수와 배열은 스택에 자동으로 할당되며, 함수가 종료되면 함께 사라진다. 반면 힙 영역은 프로그래머가 직접 요청하고 해제하는 메모리 공간으로, 프로그램이 실행되는 동안 자유롭게 크기를 조절할 수 있다는 점에서 동적이라는 이름이 붙었다. 여기서 힙(Heap)이란 운영체제가 프로세스에 부여한 메모리 영역 중 사용자가 명시적으로 요청해야만 할당되며, 함수의 생명주기와 무관하게 명시적으로 해제될 때까지 유지되는 영역을 가리킨다. 힙에 메모리를 할당하기 위해서는 stdlib.h 헤더에 정의된 표준 라이브러리 함수를 사용해야 하며, 가장 기본이 되는 함수가 바로 malloc이다.

malloc(Memory Allocation)은 인자로 받은 바이트 수만큼의 메모리 공간을 힙에 할당하고, 그 시작 주소를 void 포인터로 반환한다. 반환되는 포인터의 자료형이 void이므로, 실제로 사용할 때는 원하는 자료형의 포인터로 명시적 캐스팅을 해주는 것이 권장된다. 또한 메모리 부족 등으로 할당에 실패하면 NULL을 반환하기 때문에, 사용 전 NULL 검사를 거치는 것이 안전한 코드의 기본 원칙이다.

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

int main(void) {
    int *arr = (int *)malloc(sizeof(int) * 5);
    if (arr == NULL) { printf("실패\n"); return 1; }

    for (int i = 0; i < 5; i++) arr[i] = (i + 1) * 10;
    for (int i = 0; i < 5; i++) printf("%d ", arr[i]);  // 10 20 30 40 50

    free(arr);     // 사용 끝난 메모리 반드시 해제
    arr = NULL;    // 댕글링 포인터 방지
    return 0;
}

여기서 중요한 점은 malloc이 할당한 메모리는 초기화되지 않은 쓰레기 값을 가진다는 사실이다. 따라서 값을 읽기 전에 반드시 사용자가 직접 초기화해야 한다. 또한 sizeof(int) * 5처럼 원소의 크기와 개수를 곱해 전달하는 패턴이 표준이며, 컴파일러나 환경에 따라 자료형의 크기가 달라질 수 있어 직접 숫자를 적는 방식은 권장되지 않는다.

calloc, realloc, free의 활용

malloc 외에도 동적 메모리를 다루는 핵심 함수가 세 가지 더 있다. 시험과 실무 모두에서 자주 출제되는 항목이다.

함수 역할 초기화
malloc(size) size 바이트 할당 쓰레기 값
calloc(n, size) n × size 바이트 할당 모두 0
realloc(ptr, size) 기존 메모리 크기 변경 기존 데이터 보존
free(ptr) 메모리 해제

calloc(Contiguous Allocation)은 malloc과 거의 같지만 두 가지 차이점이 있다. 첫째, 인자를 두 개로 나누어 받는다. 둘째, 할당된 메모리 공간을 모두 0으로 자동 초기화해준다. realloc(Reallocation)은 이미 할당된 메모리의 크기를 변경할 때 사용한다. 가능하다면 같은 위치에서 크기를 늘리거나, 불가능하면 새로운 위치에 메모리를 할당하고 기존 데이터를 복사한 뒤 새 주소를 반환한다. 따라서 반환값을 반드시 다시 받아야 하며, 기존 포인터를 그대로 쓰면 위험하다.

int *arr = (int *)calloc(5, sizeof(int));   // 5개 0으로 초기화
arr = (int *)realloc(arr, sizeof(int) * 10); // 10개로 확장 (기존 5개 보존)
free(arr); arr = NULL;

마지막으로 free는 동적으로 할당받은 메모리를 시스템에 반환하는 함수이다. C언어에는 자바나 파이썬과 같은 자동 가비지 컬렉션이 없기 때문에, 할당받은 메모리는 반드시 프로그래머가 명시적으로 해제해야 한다. 해제하지 않은 메모리는 프로그램이 종료될 때까지 계속 남아 있으며, 이를 메모리 누수(Memory Leak)라 부른다. 여기서 메모리 누수란 할당 후 해제되지 못한 채 잃어버린 메모리가 누적되어 시스템 자원이 고갈되는 현상을 의미하며, 장기 실행 서버에서 가장 자주 만나는 안정성 문제이다. 또한 free를 호출한 뒤에도 포인터 변수에는 여전히 해제된 주소가 남아 있어, 이 포인터를 다시 사용하면 댕글링 포인터(Dangling Pointer) 오류가 발생한다. 이를 방지하기 위해 free 직후 포인터에 NULL을 대입하는 습관이 권장된다.

정보처리기사 실기 빈출 동적 메모리 예제

정보처리기사 실기에서 동적 메모리는 주로 malloc과 sizeof, 그리고 캐스팅을 결합한 코드 추적 문제로 출제된다(출처: Q-Net 정보처리기사).

유형 1 — malloc과 sizeof 기본.

int *p = (int *)malloc(sizeof(int) * 4);
for (int i = 0; i < 4; i++) p[i] = i + 1;
int sum = 0;
for (int i = 0; i < 4; i++) sum += p[i];
printf("%d\n", sum);    // 10 (1+2+3+4)
free(p);

p[i]*(p+i)는 완전히 동일한 표현이다. 이 등가성을 묻는 변형 문제도 자주 출제된다.

유형 2 — 구조체 동적 할당과 화살표 연산자.

typedef struct { int id; int score; } Student;
Student *s = (Student *)malloc(sizeof(Student));
s->id = 100; s->score = 95;
printf("%d %d\n", s->id, s->score);  // 100 95
free(s);

구조체를 동적 할당한 뒤에는 점 연산자가 아닌 화살표 연산자로 멤버에 접근해야 한다.

유형 3 — calloc의 자동 초기화.

int *arr = (int *)calloc(3, sizeof(int));
arr[0] = 5;
printf("%d %d %d\n", arr[0], arr[1], arr[2]);  // 5 0 0
free(arr);

유형 4 — 2차원 동적 배열.

int **mat = (int **)malloc(sizeof(int *) * 2);
for (int i = 0; i < 2; i++) {
    mat[i] = (int *)malloc(sizeof(int) * 3);
    for (int j = 0; j < 3; j++) mat[i][j] = i * 3 + j + 1;
}
printf("%d\n", mat[1][2]);   // 6

for (int i = 0; i < 2; i++) free(mat[i]);   // 행을 먼저 해제
free(mat);                                   // 행 포인터 배열을 마지막에

해제할 때도 행을 먼저 해제한 뒤 마지막에 행 포인터 배열을 해제하는 순서를 정확히 지켜야 한다. 솔직히 제 경험상 학교 알고리즘 과제에서 가장 자주 사고가 난 부분이 정확히 이 해제 순서였고, 한 번 순서를 뒤집어 실행한 후 valgrind가 줄줄이 오류를 토해내는 모습을 본 후로는 "할당의 역순으로 해제한다"는 한 줄을 손끝으로 외우게 되었다.


메타 디스크립션: C언어의 동적 메모리 할당 함수 malloc, calloc, realloc, free의 차이와 사용법, 메모리 누수와 댕글링 포인터 같은 흔한 오류 방지법, 그리고 정보처리기사 실기 빈출 4패턴을 코드 예시와 함께 정리합니다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름