security
CHAPTER 90 / 96
읽기 약 2분
FUNCTION
malloc과 free: 동적 메모리
핵심 개념
malloc/calloc/realloc/free — 메모리 누수, dangling 포인터, double free + valgrind 탐지.
본문
4가지 동적 메모리 함수
#include <stdlib.h>
// 할당
void *malloc(size_t size); // 초기화 안 된 메모리
void *calloc(size_t n, size_t size); // 0으로 초기화
void *realloc(void *ptr, size_t size); // 크기 변경
// 해제
void free(void *ptr);malloc 기본
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(10 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "할당 실패\n");
return 1;
}
// 초기화 안 됨 — 쓰레기 값
printf("초기값: %d\n", arr[0]);
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
free(arr);
arr = NULL; // dangling 방지
return 0;
}calloc — 0 초기화
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = calloc(10, sizeof(int));
if (arr == NULL) return 1;
// 모두 0
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]); // 0 0 0 0 0 0 0 0 0 0
}
printf("\n");
free(arr);
return 0;
}realloc — 크기 변경
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) arr[i] = i;
// 10개로 확장
int *new_arr = realloc(arr, 10 * sizeof(int));
if (new_arr == NULL) {
free(arr); // 원본 해제 후 종료
return 1;
}
arr = new_arr; // ⚠️ realloc 실패 시 원본 그대로 유효
// 기존 값 보존됨, 새 영역은 초기화 안 됨
for (int i = 0; i < 5; i++) printf("%d ", arr[i]);
// 0 1 2 3 4
free(arr);
return 0;
}메모리 누수
#include <stdio.h>
#include <stdlib.h>
void leak(void) {
int *p = malloc(1000 * sizeof(int));
// ⚠️ free 누락 — 함수 종료 시 메모리 누수
}
int main(void) {
for (int i = 0; i < 1000000; i++) {
leak();
}
// 4GB 누수
return 0;
}valgrind로 누수 탐지
# 컴파일
gcc -g -O0 leak.c -o leak
# valgrind 실행
valgrind --leak-check=full ./leak
# 출력 예시:
# ==12345== HEAP SUMMARY:
# ==12345== in use at exit: 4,000,000,000 bytes in 1,000,000 blocks
# ==12345== total heap usage: 1,000,000 allocs, 0 frees, 4,000,000,000 bytes allocated
# ==12345==
# ==12345== 4,000,000,000 bytes in 1,000,000 blocks are definitely lost
# ==12345== at 0x...: malloc
# ==12345== by 0x...: leak (leak.c:5)
# ==12345== by 0x...: main (leak.c:12)Dangling 포인터
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 42;
free(p);
// ❌ 위험 — p는 더 이상 유효하지 않음 (dangling)
// *p = 100; // Use-After-Free
// printf("%d\n", *p); // 정의되지 않은 동작
p = NULL; // ✅ 명시적 무효화
if (p != NULL) {
// 실행 안 됨
}
return 0;
}Double free
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
free(p);
free(p); // ❌ 두 번 해제 — 힙 손상, glibc는 중단시킴
// 출력: free(): double free detected in tcache 2
// Aborted (core dumped)
// ✅ 안전
int *q = malloc(sizeof(int));
free(q);
q = NULL; // 다시 free 호출되어도 free(NULL)은 안전
free(q); // OK
return 0;
}메모리 안전 패턴
#include <stdio.h>
#include <stdlib.h>
// 패턴 1 — 단일 진입/종료 + cleanup
int do_work(void) {
int *a = NULL, *b = NULL;
int rc = -1;
a = malloc(100);
if (!a) goto cleanup;
b = malloc(200);
if (!b) goto cleanup;
// 작업...
rc = 0;
cleanup:
free(a);
free(b);
return rc;
}
// 패턴 2 — 매크로로 free + NULL
#define SAFE_FREE(p) do { free(p); (p) = NULL; } while (0)
int main(void) {
int *p = malloc(100);
SAFE_FREE(p);
SAFE_FREE(p); // OK — free(NULL)
return 0;
}valgrind + gcc sanitizer
# valgrind (모든 OS, 별도 도구)
valgrind --leak-check=full --track-origins=yes ./prog
# AddressSanitizer (gcc/clang 내장, 빠름)
gcc -fsanitize=address -g prog.c -o prog
./prog
# 누수·UAF·OOB 모두 탐지 + 정확한 위치 + 백트레이스
# MemorySanitizer (clang)
clang -fsanitize=memory -g prog.c -o prog
# 초기화 안 된 메모리 사용 탐지다음 챕터
CH.7 "버퍼 오버플로우" — 메모리 안전의 가장 유명한 취약점.
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 C 코드의 모든 malloc/free 짝을 분석해서 누수·UAF·double free 위험 위치를 보고해줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
리눅스 커널과 일반 애플리케이션의 메모리 관리 차이 + RAII 패턴 비교를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 C 프로젝트에 valgrind/ASan을 적용하는 가이드와 자동화 스크립트를 만들어줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 임베디드 시스템에서 메모리 누수 사고 통계와 방어 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
malloc과 free: 동적 메모리는 이 3가지만 확실히 잡으세요
1.malloc/free는 짝 — 누락 시 누수, 두 번 호출 시 double free
2.안전 패턴: free 후 NULL 설정 + valgrind/ASan으로 자동 탐지
3.다음 챕터 CH.7에서 버퍼 오버플로우 — 메모리 안전의 대표 취약점
공유하기
진행도 90 / 96