malloc/free

hyunahn·2025년 12월 4일

malloc/free의 내부 동작: 힙의 이면

1. The Core Concept (본질 정의)

"malloc은 공짜가 아닙니다. 장부(Bookkeeping)를 관리하는 거대한 도서관 사서입니다."

많은 분들이 malloc(size)을 호출하면 마법처럼 메모리가 뚝 떨어진다고 생각합니다. 하지만 실제로는 C 런타임 라이브러리(Glibc, Heap Manager)가 운영체제로부터 큰 땅(Heap)을 미리 빌려놓고, 사용자가 요청할 때마다 칼로 잘라서 내어주는 것입니다.

이때, "누구에게 얼마만큼 빌려줬는지" 기록해야 하기 때문에, 실제로는 요청한 크기보다 더 많은 메모리를 사용합니다.


2. Under the Hood (하드웨어 투시)

malloc(16)을 호출했을 때 메모리에 16바이트만 생기는 것이 아닙니다. 투시해 봅시다.

A. 청크(Chunk) 구조: 메모리 속의 숨겨진 꼬리표

힙 관리자는 메모리를 청크(Chunk) 단위로 관리합니다.

[ Heap Memory Block ]

Address     | Content (What's inside)
------------+------------------------------------------
0x1000      | [ Size: 24 ] [ Flag: Used ]  <-- Metadata (Header)
------------+------------------------------------------
0x1004      | [ User Data (16 Bytes) ]     <-- ptr (반환되는 주소)
            | ...                          
0x1013      | ...                          
------------+------------------------------------------
0x1014      | [ Padding (Alignment) ]      <-- 4의 배수 맞춤
------------+------------------------------------------
0x1018      | [ Size: ... ] [ Flag: ... ]  <-- 다음 블록의 헤더
  1. Metadata (Header): 사용자에게 주는 포인터(ptr)의 바로 앞부분(4~8바이트)에는 이 블록의 크기와 사용 여부가 기록되어 있습니다.
    • 비밀: 이것이 바로 free(ptr) 함수가 크기 인자를 받지 않아도 되는 이유입니다. free는 내부적으로 ptr - Header_Size 위치를 읽어서 "아, 이 블록은 24바이트짜리구나"라고 알아냅니다.
  2. Payload: 실제 사용자가 쓰는 공간입니다.
  3. Padding: 앞서 배운 Alignment(정렬)를 위해 빈 공간을 채웁니다.

B. 프래그멘테이션(Fragmentation): 치즈 구멍 현상

메모리를 계속 할당하고 해제하다 보면, 힙 메모리는 구멍 뚫린 치즈처럼 변합니다.

  • 상황: 100바이트 힙 공간이 있습니다.
    1. A(20) 할당 -> B(20) 할당 -> C(20) 할당 -> D(20) 할당
    2. B와 D를 해제(free)
    3. 현재 남은 공간: 40바이트 (B자리 20 + D자리 20)
    4. 요청: malloc(30)
    5. 결과: 실패! (Out of Memory)
  • 총 빈 공간은 40이지만, 연속된 30바이트가 없기 때문입니다. 이것이 외부 단편화(External Fragmentation)입니다.

3. The "Why" & Real-world Analogy (이유와 비유)

Q: 왜 힙 관리가 스택보다 느리고 복잡한가요?
A: "스택은 접시 쌓기이고, 힙은 테트리스이기 때문입니다."

  • Stack: 그냥 위에 얹고(Push), 위에서 뺍니다(Pop). 구멍이 생길 일이 절대 없습니다. 계산이 필요 없습니다. (하드웨어 SP 레지스터 이동)
  • Heap:
    • 사용자가 "30칸 줘"라고 하면, 힙 관리자는 빈 공간 리스트(Free List)를 처음부터 뒤지며 30칸이 들어갈 틈을 찾습니다(Search). (속도 저하)
    • 찾아서 주고 나면 빈 공간 리스트를 업데이트합니다.
    • 나중에 반납받으면, 옆에 있는 빈 공간과 합쳐서(Coalescing) 큰 빈 공간으로 만들어야 합니다. (관리 비용)

4. Code & Best Practice

메모리 관리의 내부를 알았을 때 피해야 할 코드 패턴입니다.

1) 잦은 malloc/free는 자살행위

작은 크기의 메모리를 수천 번 malloc/free 하는 것은 최악입니다.

/* BAD Practice: 힙 단편화 유발 */
for (int i = 0; i < 1000; i++) {
    char *p = malloc(16); // 16바이트 짜리 구멍을 수없이 만듦
    // ... 작업 ...
    free(p);
}
  • 이유: 메타데이터 오버헤드가 페이로드보다 커질 수 있으며, 힙이 벌집이 되어 나중에 큰 메모리를 할당 못 하게 됩니다.

2) free 후 댕글링 포인터(Dangling Pointer)

char *ptr = malloc(10);
free(ptr); 
// ptr은 여전히 0x1004를 가리키고 있음! (집은 팔았는데 열쇠를 안 버림)

*ptr = 'A'; // Use-After-Free 버그! 
// 만약 0x1004가 다른 용도로 재할당되었다면? 치명적인 데이터 오염 발생
  • Best Practice: free 직후에 반드시 포인터를 NULL로 미십시오.
    free(ptr);
    ptr = NULL; // 안전장치

3) 메모리 누수(Memory Leak)의 진짜 공포

"프로그램 끄면 OS가 알아서 수거해가니까 괜찮지 않나요?"

  • 서버/임베디드: 이들은 안 꺼집니다. 몇 달 동안 1바이트씩 새면 결국 수 기가바이트가 되어 시스템이 멈춥니다(OOM Killer).

핵심정리:
"malloc을 한다는 건, 시스템에게 '비용이 많이 드는 부탁'을 하는 것과 같습니다. 내부적으로 메타데이터를 쓰고, 빈 공간을 탐색하는 무거운 작업이죠.
이 원리를 이해하셨다면, 왜 자원이 제한적인 임베디드 시스템이나 실시간 시스템(RTOS)에서 malloc을 악마 취급하는지 직감하실 겁니다."

0개의 댓글