C 동적 메모리 할당: malloc, calloc, realloc, free 완벽 마스터
C에서 동적 메모리 할당을 다루기 전에 C에서 메모리가 어떻게 관리되는지 이해하는 것이 필수적입니다.
C에서 메모리 관리 작동 원리
기본 타입으로 변수를 선언하면 컴파일러가 스택에 자동으로 공간을 할당합니다. 예를 들어 float는 일반적으로 4바이트(플랫폼 의존)를 차지합니다. sizeof 연산자로 확인 가능:
#include <stdio.h>
int main() {
float x;
printf("The size of float is %d bytes", sizeof(x));
return 0;
}
출력:
The size of float is 4 bytes
지정된 크기의 배열도 스택에 연속적으로 할당되며, 각 요소는 해당 타입 크기를 차지합니다:
#include <stdio.h>
int main() {
float arr[10];
printf("The size of the float array with 10 elements is %d", sizeof(arr));
return 0;
}
결과:
The size of the float array with 10 elements is 40
스택 할당은 자동이지만, 많은 프로그램은 런타임까지 크기를 알 수 없는 메모리를 필요로 합니다. 이 필요를 동적 메모리 할당이 해결하며, 스택 대신 힙을 사용합니다.
C 동적 메모리 할당
동적 할당은 필요에 따라 메모리를 수동으로 요청하고 해제하며, 포인터로 힙의 할당 블록을 추적합니다. C 표준 라이브러리는 이를 위한 여러 함수를 제공:
- malloc() – 지정된 크기의 연속된 바이트 블록 할당 후 첫 바이트 포인터 반환
- calloc() – 요소 배열 할당, 모든 바이트 0으로 초기화, 첫 요소 포인터 반환
- realloc() – 이전에 할당된 블록 크기 조정, 내용 보존
- free() – 이전에 할당된 블록을 힙에 반환
각 함수를 자세히 살펴봅시다.
malloc() 함수
malloc()은 "memory allocation"을 의미합니다. 지정된 크기의 메모리 블록을 예약하고 이에 대한 포인터를 반환합니다. 포인터는 보통 원하는 타입으로 캐스팅:
ptr = (캐스트_타입 *) malloc(바이트_크기);
예제:
#include <stdlib.h>
int main(){
int *ptr;
ptr = malloc(15 * sizeof(*ptr)); /* 15개 정수 */
if (ptr != NULL) {
*(ptr + 5) = 480; /* 6번째 요소 */
printf("Value of the 6th integer is %d", *(ptr + 5));
}
}
출력:
Value of the 6th integer is 480
핵심 포인트:
sizeof(*ptr) 사용으로 타입 변경에 강건한 코드 작성
NULL 반환 확인으로 할당 실패 감지
- 할당 메모리는 연속적; 배열처럼 다루고 포인터 연산 사용
동적 할당은 기본 타입뿐 아니라 구조체, 문자 배열 등에도 적용 가능.
free() 함수
malloc(), calloc(), realloc()로 할당된 메모리는 누수 방지를 위해 명시적으로 해제해야 합니다. free()가 이 작업을 수행:
#include <stdio.h>
int main() {
int* ptr = malloc(10 * sizeof(*ptr));
if (ptr != NULL){
*(ptr + 2) = 50;
printf("Value of the 2nd integer is %d", *(ptr + 2));
}
free(ptr);
}
출력:
Value of the 2nd integer is 50
calloc() 함수
calloc()은 동일한 크기의 여러 블록을 할당하고 모든 바이트를 0으로 초기화하므로, 제로 초기화가 필요한 배열과 구조체에 이상적:
ptr = (캐스트_타입 *) calloc(n, 크기);
예제 – 등차수열 합계:
#include <stdio.h>
int main() {
int i, *ptr, sum = 0;
ptr = calloc(10, sizeof(int));
if (ptr == NULL) {
printf("Error! memory not allocated.");
exit(0);
}
for (i = 0; i < 10; ++i) {
*(ptr + i) = i;
sum += *(ptr + i);
}
printf("Sum = %d", sum);
free(ptr);
return 0;
}
결과:
Sum = 45
calloc() vs malloc()
주요 차이점:
- 초기화:
calloc()은 메모리 제로 초기화; malloc()은 미초기화(쓰레기 값)
- 용도: 제로 값 필요한 배열/구조체는
calloc() 선호; 초기화 불필요하면 malloc()이 더 단순
- 성능: 둘 다 비슷하나
calloc()은 제로화로 약간 느릴 수 있음
realloc() 함수
realloc()은 기존 할당 크기를 변경하면서 내용 보존:
ptr = realloc(ptr, 새_크기);
예제 – 문자열 버퍼 확장:
#include <stdio.h>
int main () {
char *ptr = malloc(10);
strcpy(ptr, "Programming");
ptr = realloc(ptr, 20); // 확대
strcat(ptr, " In 'C'");
printf("%s, Address = %u", ptr, ptr);
free(ptr);
return 0;
}
재할당 실패 시 realloc()은 NULL 반환, 원본 블록은 유효한 상태 유지.
동적 배열(Dynamic Arrays)
동적 배열은 실행 중 요소 수 증가 가능, 알고리즘과 자료구조에서 흔한 패턴:
#include <stdio.h>
int main() {
int *arr_dynamic = NULL;
int elements = 2, i;
arr_dynamic = calloc(elements, sizeof(int));
for (i = 0; i < elements; i++) arr_dynamic[i] = i;
for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);
elements = 4;
arr_dynamic = realloc(arr_dynamic, elements * sizeof(int));
printf("After realloc\n");
for (i = 2; i < elements; i++) arr_dynamic[i] = i;
for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);
free(arr_dynamic);
}
결과:
arr_dynamic[0]=0
arr_dynamic[1]=1
After realloc
arr_dynamic[0]=0
arr_dynamic[1]=1
arr_dynamic[2]=2
arr_dynamic[3]=3
요약
- 동적 메모리로 런타임에 힙 공간 할당/해제 가능
- 단일 블록은
malloc(), 제로 초기화 배열은 calloc() 사용
- 할당 실패 감지를 위해 항상
NULL 확인
- 메모리 누수 방지를 위해
free()로 해제
- 더 많은 공간 필요 시
realloc()로 크기 조정
- 동적 배열은 유연한 자료구조를 위한 강력한 패턴