동적 메모리 할당
동적 메모리 할당 (Dynamic Memory Allocation)
개요
동적 메모리 할당이란 프로그램 실행 중(Runtime)에 필요한 메모리 크기를 결정하여 운영체제로부터 메모리를 할당받는 기법을 말한다.
컴파일 시점에 메모리 크기가 결정되는 정적 할당(Static Allocation)은 메모리 낭비가 발생하거나, 반대로 필요한 메모리가 부족할 경우 프로그램이 비정상 종료되는 한계가 있다. 반면 동적 할당은 데이터의 양을 미리 예측할 수 없는 상황(예: 사용자 입력에 따른 배열 크기 결정)에서 유연하게 메모리를 사용할 수 있게 하여 자원 활용 효율을 극대화한다.
메모리 구조와 힙(Heap) 영역
프로그램이 실행되면 운영체제는 프로세스를 위해 가상 메모리 공간을 할당하며, 이는 크게 네 가지 영역으로 나뉜다.
- 코드(Code) 영역: 실행할 프로그램의 기계어 명령어가 저장되는 영역이다.
- 데이터(Data) 영역: 전역 변수와 정적 변수(static)가 저장되는 영역이다.
- 스택(Stack) 영역: 지역 변수와 함수 호출 시의 리턴 주소, 매개변수가 저장되는 영역으로, LIFO(Last-In-First-Out) 구조로 자동 관리된다.
- 힙(Heap) 영역: 사용자가 직접 할당하고 해제하는 메모리 영역으로, 런타임에 크기가 결정되는 동적 메모리가 저장된다.
[표] 스택(Stack) vs 힙(Heap) 비교
| 구분 | 스택 (Stack) | 힙 (Heap) |
|---|---|---|
| 할당 시점 | 컴파일 타임 (정적) | 런타임 (동적) |
| 관리 방식 | OS/컴파일러가 자동 관리 | 개발자가 직접 관리 (수동) |
| 할당 속도 | 매우 빠름 | 상대적으로 느림 |
| 크기 제한 | 제한적 (Stack Overflow 위험) | 시스템 가용 메모리만큼 확장 가능 |
| 데이터 수명 | 함수 종료 시 자동 소멸 | free 또는 delete 호출 전까지 유지 |
동적 할당의 작동 원리
동적 할당은 프로그램이 운영체제(OS)의 메모리 관리자에게 특정 크기의 메모리 블록을 요청하면서 시작된다.
- 요청: 프로그램이 할당 함수를 호출하여 필요한 바이트(Byte) 수를 요청한다.
- 탐색: OS는 힙 영역에서 요청된 크기를 수용할 수 있는 빈 공간을 찾는다.
- 할당: 적절한 공간을 찾으면 해당 영역을 '사용 중'으로 표시하고, 그 영역의 시작 주소를 반환한다.
- 참조: 프로그램은 반환된 주소를 포인터(Pointer) 변수에 저장하여 해당 메모리 영역에 접근한다. 포인터는 메모리 주소를 저장하는 특수 변수로, 동적 할당된 메모리를 제어하는 유일한 수단이 된다.
주요 함수 및 사용법
C와 C++ 언어에서는 서로 다른 방식의 할당/해제 함수를 제공한다.
C 언어 (malloc, free)
malloc(size_t size): 요청한 바이트만큼 메모리를 할당하며,void*타입을 반환하므로 적절한 타입 캐스팅이 필요하다. 메모리 할당에 실패할 경우NULL포인터를 반환하므로, 이에 대한 예외 처리(NULL 체크)가 필수적이다.free(void* ptr): 할당된 메모리를 해제하여 힙 영역으로 반환한다.
[코드 예제] C 기반 동적 할당 및 해제
#include <stdio.h>
#include <stdlib.h>
int main() {
int size;
printf("할당할 배열의 크기를 입력하세요: ");
scanf("%d", &size);
// 1. 메모리 할당 및 타입 캐스팅
int* arr = (int*)malloc(sizeof(int) * size);
// 2. 할당 실패 시 NULL 체크
if (arr == NULL) {
fprintf(stderr, "메모리 할당 실패\n");
return 1;
}
// 3. 메모리 사용
for (int i = 0; i < size; ++i) {
arr[i] = i * 10;
}
// 4. 메모리 해제
free(arr);
arr = NULL; // 댕글링 포인터 방지
return 0;
}
C++ 언어 (new, delete)
new: 객체의 크기를 자동으로 계산하여 할당하며, 생성자(Constructor)를 호출한다.delete: 메모리를 해제하며, 소멸자(Destructor)를 호출한다.
[코드 예제] C++ 기반 동적 할당 및 해제
#include <iostream>
int main() {
int size;
std::cout << "할당할 배열의 크기를 입력하세요: ";
std::cin >> size;
// 1. 메모리 할당 (할당 실패 시 예외 처리 포함)
int* arr = nullptr;
try {
arr = new int[size]; // 정수형 size개만큼 힙 영역에 할당
} catch (const std::bad_alloc& e) {
std::cerr << "메모리 할당 실패: " << e.what() << std::endl;
return 1;
}
// 2. 메모리 사용
for (int i = 0; i < size; ++i) {
arr[i] = i * 10;
}
// 3. 메모리 해제 (반드시 수행해야 함)
delete[] arr;
arr = nullptr; // 댕글링 포인터 방지를 위해 null 처리
return 0;
}
메모리 단편화 (Fragmentation)
메모리 단편화란 메모리 할당과 해제가 반복되면서, 전체 여유 공간은 충분함에도 불구하고 연속된 큰 공간이 없어 할당 요청이 거절되는 현상을 말한다.
- 내부 단편화 (Internal Fragmentation): 할당된 메모리 블록이 요청한 크기보다 커서, 블록 내부에서 낭비되는 공간이 발생하는 현상이다.
- 외부 단편화 (External Fragmentation): 작은 빈 공간들이 힙 영역 여기저기에 흩어져 있어, 총합은 크지만 연속적인 큰 블록을 만들 수 없는 현상이다.
- 해결책: 메모리 압축(Compaction)이나 페이징(Paging) 기법을 통해 논리적 주소와 물리적 주소를 분리하여 해결한다.
메모리 관리의 주의점과 위험성
수동 메모리 관리는 개발자의 실수로 인해 심각한 런타임 오류를 유발할 수 있다.
- 메모리 누수 (Memory Leak):
malloc이나new로 할당한 메모리를free나delete로 해제하지 않아, 사용 가능한 메모리가 점차 줄어드는 현상이다. 장시간 실행되는 서버 프로그램에서 치명적이다. - 댕글링 포인터 (Dangling Pointer): 이미 해제된 메모리 주소를 여전히 가리키고 있는 포인터를 말한다. 이 포인터를 통해 메모리에 접근하면 이미 다른 데이터가 저장되어 있거나 접근 권한이 없어 예측 불가능한 동작이 발생하거나 프로그램이 충돌할 수 있다.
- 세그멘테이션 폴트 (Segmentation Fault): 프로그램이 자신에게 할당되지 않은 메모리 영역(예: NULL 포인터, 이미 해제된 영역)에 접근하려 할 때 OS가 프로세스를 강제 종료시키는 오류이다.
현대적 메모리 관리 기법
수동 관리의 위험성을 줄이기 위해 현대 프로그래밍 언어들은 자동화된 관리 기법을 도입했다.
가비지 컬렉션 (Garbage Collection, GC)
더 이상 참조되지 않는 메모리 영역을 런타임 환경(JVM, V8 엔진 등)이 자동으로 찾아내어 해제하는 방식이다. Java, Python, JavaScript 등이 이 방식을 사용하며, 개발자의 부담을 줄이지만 GC 작동 시 프로그램이 일시 정지되는 'Stop-the-world' 현상이 발생할 수 있다.
스마트 포인터 (Smart Pointer)
C++11부터 도입된 개념으로, 객체의 참조 횟수를 계산(Reference Counting)하여 참조 횟수가 0이 되는 순간 자동으로 메모리를 해제하는 래퍼(Wrapper) 클래스이다. 대표적으로 std::unique_ptr(독점 소유), std::shared_ptr(공유 소유), std::weak_ptr(약한 참조) 등이 있다.
[표] 언어별 메모리 관리 방식 비교
| 언어 | 관리 방식 | 특징 | 주요 도구/기법 |
|---|---|---|---|
| C | 완전 수동 | 최대의 제어권, 높은 위험성 | malloc, free |
| C++ | 수동 $\rightarrow$ 반자동 | 성능과 편의성의 절충 | new, delete, 스마트 포인터 |
| Java | 자동 (GC) | 개발 생산성 높음, 런타임 오버헤드 | JVM Garbage Collector |
| Python | 자동 (GC) | 매우 높은 편의성, 상대적 느린 속도 | Reference Counting, GC |
| Rust | 소유권 시스템 | GC 없이 메모리 안전성 보장 | Ownership, Borrow Checker |
이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.
주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.