정적 메모리(int arr[100])는 크기가 고정되어 있어 런타임 중에 크기를 바꿀 수 없다.
반면, 동적 메모리는 실행 중 필요한 만큼 메모리를 요청해서 사용하는 방식이다.
예를 들어 사용자 입력에 따라 배열 크기를 정하거나, 구조체 배열을 유동적으로 늘릴 때 필요하다.
void *malloc(size_t size);
지정한 바이트 크기만큼 메모리를 할당하고, 그 주소를 반환한다.
실패 시 NULL을 반환한다.
반환된 메모리는 초기화되지 않는다 (쓰레기값 존재 가능).
예제
int *arr = (int *)malloc(5 * sizeof(int));
if (arr == NULL) {
// 메모리 할당 실패 처리
}
arr[0] = 10;
| sizeof(type)을 이용해 정확한 바이트 수를 계산하는 것이 중요하다.
void *calloc(size_t n, size_t size);
n * size 바이트만큼 메모리를 할당하고, 모든 값을 0으로 초기화한다.
내부적으로 malloc + memset과 유사한 동작을 한다.
예제
int *arr = (int *)calloc(5, sizeof(int));
arr[0] == 0; // 초기화된 값
| 구조체나 배열을 할당할 때 유용하다. 단, 초기화 비용이 있기 때문에 성능이 중요한 경우에는 malloc을 선호하기도 한다.
void *realloc(void *ptr, size_t new_size);
이미 할당된 메모리 블록을 새로운 크기로 재할당한다.
기존 내용은 유지된다.
주소가 바뀔 수 있으므로 반환값으로 받은 새 포인터를 꼭 사용해야 한다.
예제
int *arr = (int *)malloc(3 * sizeof(int));
arr[0] = 1; arr[1] = 2; arr[2] = 3;
int *new_arr = (int *)realloc(arr, 6 * sizeof(int));
if (new_arr != NULL) {
arr = new_arr;
arr[3] = 4;
}
| realloc은 내부적으로 malloc → memcpy → free와 비슷하게 동작한다. 주소가 바뀔 수 있으므로, 반드시 새 포인터로 덮어써야 한다.
free(ptr);
malloc, calloc, realloc로 할당한 메모리를 해제한다.
해제 후 해당 포인터를 다시 사용하면 안 된다(댕글링 포인터 위험).
int *p = malloc(10);
free(p);
p = NULL; // 이 처리가 안전
| 함수 | 역할 | 초기화 여부 | 사이즈 인자 | 주의점 |
|---|---|---|---|---|
malloc | 메모리 할당 | ❌ | 1개 (바이트 수) | 쓰레기값 |
calloc | 메모리 할당 | ✅ | 2개 (갯수, 크기) | 느릴 수 있음 |
realloc | 크기 재조정 | 유지 | 새 크기 | 주소 바뀔 수 있음 |
free | 메모리 해제 | 해당 없음 | 없음 | 해제 후 NULL 처리 권장 |
동적 할당은 직접 관리해야 하기 때문에, 할당 후 반드시 해제해야 한다.
해제하지 않으면 메모리 누수가 발생하고, 반복되면 시스템 리소스를 잡아먹는다.
| valgrind 같은 도구로 메모리 누수를 확인할 수 있다.