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

동적 메모리 할당의 개념과 malloc
C언어 프로그램의 메모리 공간은 크게 코드 영역, 데이터 영역, 스택(Stack), 힙(Heap)으로 구분된다. 일반적인 지역 변수와 배열은 스택에 자동으로 할당되며, 함수가 종료되면 함께 사라진다. 반면 힙 영역은 프로그래머가 직접 요청하고 해제하는 메모리 공간으로, 프로그램이 실행되는 동안 자유롭게 크기를 조절할 수 있다는 점에서 동적이라는 이름이 붙었다. 힙에 메모리를 할당하기 위해서는 stdlib.h 헤더에 정의된 표준 라이브러리 함수를 사용해야 하며, 가장 기본이 되는 함수가 바로 malloc이다.
malloc(Memory Allocation)은 인자로 받은 바이트 수만큼의 메모리 공간을 힙에 할당하고, 그 시작 주소를 void 포인터로 반환한다. 반환되는 포인터의 자료형이 void이므로, 실제로 사용할 때는 원하는 자료형의 포인터로 명시적 캐스팅을 해주는 것이 권장된다. 또한 메모리 부족 등으로 할당에 실패하면 NULL을 반환하기 때문에, 사용 전 NULL 검사를 거치는 것이 안전한 코드의 기본 원칙이다.
#include <stdio.h>
#include <stdlib.h>
int main() {
// int 5개를 담을 메모리 동적 할당
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 외에도 동적 메모리를 다루는 핵심 함수가 세 가지 더 있다. calloc, realloc, free이며, 각각의 역할이 분명히 다르므로 상황에 맞게 선택해야 한다. calloc(Contiguous Allocation)은 malloc과 거의 같지만 두 가지 차이점이 있다. 첫째, 인자를 두 개로 나누어 받는다. 즉 원소의 개수와 한 원소의 크기를 따로 전달한다. 둘째, 할당된 메모리 공간을 모두 0으로 자동 초기화해준다. 따라서 정수 배열을 모두 0으로 시작하고 싶을 때 매우 편리하다.
// 정수 5개를 0으로 초기화된 상태로 할당
int *arr = (int *)calloc(5, sizeof(int));
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 0 0 0 0 0
}
free(arr);
realloc(Reallocation)은 이미 할당된 메모리의 크기를 변경할 때 사용한다. 첫 인자로 기존 포인터를, 두 번째 인자로 새 크기를 전달하면 가능하다면 같은 위치에서 크기를 늘리거나, 불가능하면 새로운 위치에 메모리를 할당하고 기존 데이터를 복사한 뒤 새 주소를 반환한다. 따라서 반환값을 반드시 다시 받아야 하며, 기존 포인터를 그대로 쓰면 위험하다.
int *arr = (int *)malloc(sizeof(int) * 3);
arr[0] = 1; arr[1] = 2; arr[2] = 3;
// 크기를 5개로 확장
arr = (int *)realloc(arr, sizeof(int) * 5);
arr[3] = 4; arr[4] = 5;
for (int i = 0; i < 5; i++) printf("%d ", arr[i]); // 1 2 3 4 5
free(arr);
마지막으로 free는 동적으로 할당받은 메모리를 시스템에 반환하는 함수이다. C언어에는 자바나 파이썬과 같은 자동 가비지 컬렉션이 없기 때문에, 할당받은 메모리는 반드시 프로그래머가 명시적으로 해제해야 한다. 해제하지 않은 메모리는 프로그램이 종료될 때까지 계속 남아 있으며, 이를 메모리 누수(Memory Leak)라 부른다. 메모리 누수가 누적되면 시스템 자원이 고갈되어 프로그램이 비정상적으로 느려지거나 강제 종료될 수 있다. 또한 free를 호출한 뒤에도 포인터 변수에는 여전히 해제된 주소가 남아 있어, 이 포인터를 다시 사용하면 댕글링 포인터(Dangling Pointer) 오류가 발생한다. 이를 방지하기 위해 free 직후 포인터에 NULL을 대입하는 습관이 권장된다.
정보처리기사 실기 빈출 동적 메모리 예제
정보처리기사 실기에서 동적 메모리는 주로 malloc과 sizeof, 그리고 캐스팅을 결합한 코드 추적 문제로 출제된다. 다음 네 가지 패턴을 정확히 이해하면 안정적인 점수원이 된다.
유형 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);
malloc으로 받은 포인터는 배열처럼 인덱스 접근이 가능하며, 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);
calloc이 0으로 초기화한다는 점을 활용한 출력 추적 문제이다. malloc과의 차이를 정확히 구분할 수 있어야 한다.
유형 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);
이중 포인터로 행마다 따로 할당하는 가변 길이 2차원 배열의 표준 패턴이다. 해제할 때도 행을 먼저 해제한 뒤 마지막에 행 포인터 배열을 해제하는 순서를 정확히 지켜야 한다.
이 네 가지 패턴을 손으로 직접 추적해보면 동적 메모리 영역의 시험 대비는 충분하다. 결국 동적 메모리는 필요할 때 받고, 다 쓰면 반드시 돌려준다는 단순한 원칙으로 요약된다. malloc과 free의 짝이 항상 일치해야 한다는 사실, 그리고 NULL 검사와 free 후 NULL 대입이라는 안전 패턴을 몸에 익히면 메모리 누수 없는 견고한 C 코드를 작성할 수 있다. 이 토대 위에서 다음 글의 파일 입출력과 자료구조의 동적 노드 관리로 자연스럽게 이어진다.
메타 디스크립션: C언어의 동적 메모리 할당 함수 malloc, calloc, realloc, free의 차이와 사용법, 메모리 누수와 댕글링 포인터 같은 흔한 오류 방지법, 그리고 정보처리기사 실기 빈출 4패턴을 코드 예시와 함께 정리합니다.