에듀테크 프로젝트 대시보드
메모리 할당자 (malloc)
프로그램이 malloc() 으로 요청한 크기의 공간을 힙(heap) 에서 잘라 주고,
free() 로 돌려받는다. 이 과정에서 생기는 단편화(fragmentation) 와,
빈 공간을 관리하는 free list, 그리고 어떤 빈칸을 고를지 정하는 배치 전략을 직접 만져 본다.
운영체제는 프로세스에게 큰 연속 메모리 영역(힙)을 준다. 할당자는 이 영역을
블록(block) 단위로 쪼개 관리한다. 각 블록 앞에는 헤더가 붙어
"이 블록의 크기 / 사용 중인지" 를 기록한다. free(p) 는 포인터 바로 앞의
헤더를 보고 크기를 알아낸다 — 그래서 크기를 안 넘겨줘도 된다.
요청 크기는 보통 8/16바이트 정렬(alignment) 단위로 올림된다. 13바이트를 요청해도 16바이트를 주는 식인데, 이때 쓰이지 않고 버려지는 3바이트가 내부 단편화 다.
아래는 64칸짜리 힙이다. 크기를 정해 malloc 을 누르면 전략에 맞는 빈칸에 색 블록이
생기고, 색 블록을 클릭하면 free 된다. 인접한 빈칸은 자동으로 병합(coalescing) 된다.
빈칸 총합은 넉넉한데도 큰 블록이 안 들어가는 순간이 외부 단편화 다.
사용 중인 블록은 프로그램이 포인터로 들고 있으니 할당자가 신경 쓸 필요가 없다.
할당자가 추적해야 하는 건 빈 블록들뿐이다. 빈 블록의 본문 공간에 "다음 빈 블록 주소"를
적어 두면, 추가 메모리 없이 연결 리스트가 만들어진다. malloc 은 이 리스트를
훑어 맞는 칸을 찾고, free 는 이 리스트에 블록을 다시 매단다.
| 전략 | 고르는 칸 | 장점 | 약점 |
|---|---|---|---|
| First-fit | 앞에서부터 처음 맞는 칸 | 탐색이 빠름 | 앞쪽에 작은 조각이 쌓임 |
| Best-fit | 남는 공간이 최소인 칸 | 큰 칸을 아껴 둠 | 전부 훑어야 함, 아주 작은 조각 양산 |
| Worst-fit | 가장 큰 칸 | 남는 조각이 그나마 큼 | 큰 칸이 금방 사라짐, 성능 나쁨 |
실제 할당자(glibc ptmalloc, jemalloc, tcmalloc)는 크기별로 빈 블록을
버킷(bins/size class) 으로 나눠 관리해 탐색을 O(1) 에 가깝게 만들고, 스레드마다
arena 를 둬 락 경합을 줄인다.
// 블록 헤더: 포인터 바로 앞 8바이트에 숨어 있다 typedef struct Block { size_t size; // 본문 크기 int free; // 1이면 빈 블록 struct Block *next; // free list 연결 } Block; void *my_malloc(size_t want) { want = align8(want); // 8바이트 정렬 → 내부 단편화 for (Block *b = free_head; b; b = b->next) { if (b->free && b->size >= want) { // first-fit split_if_large(b, want); // 남으면 잘라서 새 빈 블록 b->free = 0; return (char*)b + sizeof(Block); } } return extend_heap(want); // 부족하면 sbrk/mmap 으로 확장 } void my_free(void *p) { Block *b = (Block*)((char*)p - sizeof(Block)); b->free = 1; coalesce(b); // 인접한 빈 블록과 병합 }
댕글링 포인터(free 후 사용), 이중 free, 버퍼 오버플로로 헤더 훼손 같은 버그가
모두 이 구조 위에서 일어난다. 그래서 use-after-free 는 대표적인 보안 취약점이 된다.