C 언어 시리즈 #09 — 동적 메모리·단편화·할당기·Sanitizer·프로덕션 패턴
이 글의 핵심
힙이 왜 단편화되는지, realloc이 왜 포인터를 바꿀 수 있는지, 이중 해제·UAF가 왜 UB인지, 서버·임베디드에서 각각 어떤 할당 전략을 쓰는지 설명합니다.
시리즈 안내
#09 | 📋 전체 목차 | 이전: #08 전처리기 · 다음: #10 컴파일·링크
C에서 메모리는 “주소 한 개”로 끝나지 않습니다. 프로세스 가상 주소 공간의 어디에 무엇이 올라가는지, 스택 프레임이 어떻게 쌓이는지, 힙 할당기가 내부적으로 어떤 메타데이터를 붙이는지까지 엮어 이해해야 실제 버그(누수, 힙 손상, 미정의 동작)를 재현·수정할 수 있습니다. 이 글은 시리즈 #02 타입·정렬, #06 배열·문자열, #07 구조체·ABI에서 쌓은 전제를 바탕으로, 동적 메모리와 그 주변을 실전 관점에서 정리합니다. 다만 순서는 “교과서 머리”가 아니라, 현장에서 제일 아프게 맞는 것부터 씁니다.
1. “메모리 릭으로 서버가 점점 느려지던…”
멀지 않은 과거의 이야기입니다. 메모리 누수로 서버가 점점 느려지던 주말 온콜(on-call) 경험을 떠올려 봅니다. 처음에는 p95 지연이 살짝 늘어나는 정도였고, 캐시 적중률이 좋아서 그런가보다 하고 넘어가다가, 며칠 뒤에는 OOM 킬이나 스왑 지옥을 겪게 되는 흐름입니다. top이나 htop에서 RSS만 보고 “아직 여유가 있다”고 판단했다가, 실제로는 누수 또는 캐시의 무한 성장이 누적된 것을 뒤늦게 깨닫는 경우가 많습니다. 문제를 알아차렸을 때는 이미 상황이 상당히 악화된 뒤인 경우가 대부분입니다.
힙 누수는 스택 오버플로보다 덜 극적이어서 잡기가 더 까다롭습니다. 어쩌다 한 번 죽는 스택 오버플로나 세그폴트는 로그에 바로 흔적이 남는 반면, 누수는 “천천히” 자산을 빼앗기므로, 원인이 할당인지 캐시 정책인지 외부 큐인지 감이 잘 잡히지 않습니다.
여기서 자주 강조하는 한 문장이 있습니다. malloc을 소스 코드 곳곳에 직접 쓰지 말고, 래퍼(팀 API)로 모으라는 뜻입니다. 이유는 단순합니다. 누수, 이중 free, realloc 실패는 전부 “한 줄”의 문제가 아니라 패턴으로 퍼지기 때문입니다. 래퍼 함수에만 로깅, 널 체크, 성공/실패 통계, 디버그 빌드용 계측을 넣어 두면, 나중에 “누가 malloc을 이렇게 많이 호출했는가”를 손쉽게 확인할 수 있습니다. 직접 malloc 호출을 전면 금지하는 것이 과한 팀도 있을 수 있습니다. 그렇다면 최소한 “라이브러리 경계·서비스 핵심 루프”에서만이라도 래퍼로 묶어 두는 것을 권장합니다. 이것이 프로덕션 운영에서 쌓이는 실무 감각입니다.
2. 누수가 아픈 이유, 그리고 대표 원인
가장 아픈 지점은 누수가 성능을 갉아먹는다는 점입니다. OS가 힙을 키우고, 페이지 폴트가 늘어나고, GC는 없는데 해제만 이루어지지 않으니 RSS가 계속 올라갑니다. 말 그대로 “조용한 장애”입니다.
대표적인 원인을 사례로 정리해 보겠습니다. 어떤 팀에서는 malloc만 호출하고 free는 성공 경로에서만 작성했습니다. 에러 경로에서는 함수를 바로 반환해버려 블록이 그대로 남는 문제가 생겼습니다. 또 다른 팀에서는 realloc을 p = realloc(p, n) 형태로 사용하다가, 실패 시 기존 p를 잃어버려 누수와 널 포인터 문제가 동시에 발생했습니다. 자료구조에 포인터를 넣어 두고 소유권을 문서화하지 않으면, 코드 리뷰에서 “여기서 free를 호출해야 하나요?”라는 질문이 계속 반복됩니다. longjmp나 깊은 중첩 return만 있는 코드에서는 cleanup 한 줄이 빠지는 것만으로도 누수 한 건이 그대로 남습니다.
탐지는 ASan의 누수 옵션, Valgrind, 정적 분석 도구를 조합해야 신뢰할 수 있습니다. “한 번 Valgrind를 돌려봤으니 됐다”고 판단했던 팀도 있었지만, 부하가 충분하지 않아 문제를 재현하지 못한 사례도 있었습니다. 그래서 이러한 검사는 CI 파이프라인에 상시 포함하는 것이 바람직합니다.
방지의 핵심은 소유권을 명확히 하는 것입니다. create/destroy 쌍, 단일 소유자(single owner) 원칙, goto cleanup이나 매크로 등 팀의 코딩 컨벤션에 맞는 하나의 스타일로 고정하십시오. free 이후 포인터에 NULL을 대입하는 작업은 safe_free와 같은 래퍼 함수에 넣어 처리하는 것이 좋으며, 코드 곳곳에 직접 free를 흩뿌리지 않는 것이 관리에 유리합니다.
static void buffer_free(char **p) {
if (p && *p) {
free(*p);
*p = NULL;
}
}
3. 댕글링·이중 free — 누수 다음으로 난제
누수가 “자산”을 서서히 갉아먹는 문제라면, 댕글링 포인터와 이중 free는 “데이터”를 파괴하는 지뢰와 같습니다. free 이후 같은 포인터를 읽으면 UAF(Use-After-Free)가 발생하고, free를 두 번 호출하면 이중 해제(double free)가 발생합니다. 둘 다 미정의 동작(UB)이며, 문제가 한참 뒤에 드러나는 경우가 많습니다.
free 직후 p = NULL을 대입하는 방식은 해당 포인터가 유일한 참조일 때만 안전합니다. 다른 변수가 같은 블록을 가리키고 있다면 이 방식만으로는 소용이 없습니다. “이 함수가 free를 호출하는지 여부”는 API 문서에 명확히 기록해 두는 것이 래퍼 기반 코드 스타일과 잘 맞습니다.
void use_after_free_example(void) {
int *p = (int *)malloc(sizeof *p);
if (!p) {
return;
}
*p = 42;
free(p);
p = NULL;
/* *p 읽기 금지 — UB */
}
이중 free는 힙 메타데이터를 훼손시켜 훨씬 나중에 예기치 못한 문제를 일으킵니다. safe_free와 같은 래퍼 함수를 사용하면 이중 해제를 어느 정도 줄일 수 있습니다.
void safe_free(void **pp) {
if (pp && *pp) {
free(*pp);
*pp = NULL;
}
}
4. 이제 기초로: 프로세스 메모리(스택·힙·데이터·코드)
누수와 UAF를 이해하기 위한 배경 지식으로, 프로세스 메모리의 전체 구조를 간단히 살펴보겠습니다. 운영체제와 런타임이 보여주는 구체적인 배치는 플랫폼마다 다르지만, 전형적인 사용자 모드에서는 코드 영역, 초기화/비초기화 데이터 영역, 힙, 스택이 함께 존재합니다. 주소가 증가하는 방향은 아키텍처마다 다를 수 있으므로, 방향 자체보다는 각 영역의 역할에 집중하는 것이 중요합니다.
4.1 개념 다이어그램
높은 주소
+--------------------------+
| 스택 (Stack) | <- 지역 변수, 함수 인자, 반환 주소
| (자동 저장, LIFO) |
+--------------------------+
| ↓ 성장 |
| (미사용 가드) |
| ↑ 성장 |
+--------------------------+
| 힙 (Heap) | <- malloc 계열, mmap 기반 큰 블록 등
| (수동 수명 관리) |
+--------------------------+
| BSS (비초기화 전역/정적) | <- 0으로 암시 초기화되는 데이터
+--------------------------+
| Data (초기화 전역/정적) | <- 상수가 아닌 초기값이 있는 전역
+--------------------------+
| RO Data (문자열 리터럴 등) | <- 읽기 전용 데이터(플랫폼에 따라 분리)
+--------------------------+
| Text (코드, 읽기 실행) | <- 기계어, 상수 풀 일부
+--------------------------+
낮은 주소
Mermaid로 정리:
flowchart TB
subgraph addr["가상 주소 공간(개념)"]
S[스택: 지역·프레임·반환 주소]
H[힙: malloc/free·수명은 프로그래머 책임]
B[BSS: 비초기화 static/global]
D[Data: 초기화 static/global]
R[RO: const·리터럴·읽기 전용]
T[Text: 실행 코드]
end
S --> H
H --> B
B --> D
D --> R
R --> T
코드(Text) 영역은 대체로 읽기·실행 전용이며, BSS/Data 영역은 전역·정적 변수를 담고, 힙은 malloc 계열 함수와 그 메타데이터가 위치하는 곳이며, 스택은 함수 호출마다 프레임 단위로 지역 변수와 인자가 쌓이는 공간입니다. “큰 버퍼는 힙에 할당한다”와 같은 팀 규칙은 바로 이 구조에서 비롯됩니다. 서버 환경에서는 “스택 오버플로는 해당 스레드에 할당된 한도 내에서 발생한다”는 점을 알아두면, 멀티스레드 환경에서 장애를 분석할 때 유용합니다.
5. 스택: 지역·프레임·스택 오버플로
지역 변수는 함수 프레임 안에 위치하며, 수명은 해당 블록이 끝날 때까지입니다. 스택 공간은 유한하므로, 깊은 재귀 호출이나 입력 크기에 따라 커지는 VLA(가변 길이 배열)는 위험할 수 있습니다. 실제 서비스 코드에서는 크기에 상한을 두고, 이를 초과하면 에러를 반환하거나 힙으로 옮기는 방식을 사용합니다.
void risky(const char *path) {
/* VLA는 스택을 크게 먹을 수 있음 — 입력 의존 크기면 특히 위험 */
size_t n = read_size_from_untrusted(path); /* 가정: 외부 입력 */
char buf[n]; /* VLA: 플랫폼/컴파일러에 따라 한도 초과 시 UB */
(void)buf;
}
한 줄로 요약하면, 스택은 자동으로 관리되고 비용이 낮지만 한도가 있으며, 힙은 자유롭게 쓸 수 있는 대신 할당 실패·단편화·동기화·버그 가능성이 늘어난다는 점입니다. 이는 C 메모리 관리의 기본 전제이므로 반드시 기억해 두어야 합니다.
6. 힙: malloc·calloc·realloc (래퍼 안에서만 보자)
malloc(size)는 연속된 공간을 반환하며, 기본적으로 max_align_t 수준의 정렬을 보장합니다. 다만 내용은 초기화되지 않으므로, 이전에 사용되던 값이 그대로 남아 있을 수 있습니다. 이를 0으로 가정하고 코드를 작성하면 심각한 버그로 이어질 수 있습니다.
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = (int *)malloc(sizeof *p * 4);
if (!p) {
perror("malloc");
return 1;
}
/* 반드시 초기화 */
for (int i = 0; i < 4; ++i) {
p[i] = i;
}
free(p);
return 0;
}
calloc(nmemb, size)는 곱셈 과정에서 발생할 수 있는 오버플로를 API 차원에서 방지하려는 설계입니다(의미상으로는 “0으로 채운 배열”을 반환합니다). 다만 민감한 데이터를 다루는 “보안”을 대체하는 수단은 아니므로, FAQ에서도 언급했듯 이 점을 착각하지 않아야 합니다.
realloc은 먼저 제자리 확장을 시도하고, 실패하면 새 위치로 이동하며 이 경우 기존 ptr은 무효가 됩니다. p = realloc(p, n)처럼 반환값을 원래 포인터에 바로 대입하는 코드는 실패 시 누수를 일으키는 전형적인 실수이므로, 임시 포인터에 먼저 받는 패턴을 사용해야 합니다. 이 패턴을 래퍼 함수에 넣어두면 팀 전체가 같은 실수를 반복하지 않습니다.
/* 위험: 실패 시 기존 블록 누수 + ptr 손실 */
/* p = realloc(p, newsize); */
void *tmp = realloc(p, newsize);
if (!tmp) {
perror("realloc");
return -1;
}
p = tmp;
할당 실패는 서버, CLI, 임베디드 환경을 가리지 않고 모두 현실적으로 발생할 수 있습니다. 널 체크, 상위 함수로의 에러 전파, 부분 할당에 대한 롤백 처리가 필요합니다. 아래 IntVec 예제처럼 부분 할당 실패 시 free(v)를 호출하는 것을 잊지 않아야 합니다. 실무에서는 여기에 xmalloc과 같은 래퍼 함수와 로깅을 함께 적용하는 팀이 많습니다.
typedef struct {
int *data;
size_t len;
} IntVec;
IntVec *intvec_create(size_t n) {
IntVec *v = (IntVec *)malloc(sizeof *v);
if (!v) {
return NULL;
}
v->data = (int *)malloc(sizeof *v->data * n);
if (!v->data) {
free(v); /* 부분 할당 정리 */
return NULL;
}
v->len = n;
return v;
}
void intvec_destroy(IntVec *v) {
if (!v) {
return;
}
free(v->data);
free(v);
}
특수한 정렬이 필요한 경우에는 aligned_alloc과 같은 표준 또는 플랫폼 API를 사용해야 합니다. 정렬을 잘못 가정하면 SIMD 연산이나 DMA 전송에서 문제를 일으킬 수 있습니다.
#include <stdlib.h>
#include <stdio.h>
int main(void) {
void *p = aligned_alloc(64, 1024);
if (p == NULL) {
perror("aligned_alloc");
return 1;
}
free(p);
return 0;
}
7. 단편화·메타데이터(힙의 현실)
할당기는 자유 블록을 리스트·버디·TLSF 등의 방식으로 관리합니다. 작은 크기의 malloc/free가 빈번하게 발생하면 내부·외부 단편화가 커지며, “여유 공간은 있는데 malloc이 실패”하는 상황이 나타날 수 있습니다. 이를 완화하는 방법으로는 아레나(한 번에 통째로 해제), 일괄 처리(batching), 풀(pool) 등이 있으며, GC가 없는 C에서는 이러한 전략 자체가 아키텍처 설계의 영역에 가깝습니다. 그래서 앞서 언급한 할당 래퍼에 “이 경로는 아레나만 사용한다”는 규칙을 명시해두는 팀도 있습니다.
8. ASan·Valgrind·malloc 통계
AddressSanitizer는 섀도 메모리를 이용해 OOB(범위 밖 접근), UAF, 이중 free를 잡아냅니다. CI에는 -fsanitize=address 옵션을 추가합니다.
clang -fsanitize=address -g -O1 main.c -o main
./main
Valgrind Memcheck은 실행 속도는 느리지만 기준선(baseline) 검사 도구로 유용합니다. 미초기화 메모리 읽기, 잘못된 free 호출, 누수를 탐지할 수 있어 야간 배치 작업에도 적합합니다.
valgrind --leak-check=full --track-origins=yes ./a.out
malloc_stats나 mallopt와 같은 함수는 glibc에 종속적이어서 이식성은 떨어지지만, “힙이 왜 이 크기까지 커졌는가”를 점검할 때 한 번쯤은 도움이 됩니다. 사용할 때는 문서와 실제 출력을 반드시 대조해 확인해야 합니다.
9. 풀·아레나·스레드 로컬/대체 할당기
같은 크기의 객체를 매우 자주 할당한다면, 큰 블록을 미리 확보하고 자유 리스트로 관리하는 슬랩(slab) 방식이 단편화와 성능 두 측면에서 모두 유리할 수 있습니다. 다만 스레드 안전성, 정렬, ASan과의 호환성은 별도로 설계해야 합니다. 요청 처리 한 번의 주기 안에서만 사용되는 객체라면, malloc을 수십 번 호출하는 대신 아레나에서 순차적으로(bump) 할당하고 끝에 한 번에 해제하는 패턴이 HTTP 서버, 게임, 파서 등에서 흔히 사용됩니다.
typedef struct {
char *base;
size_t cap;
size_t off;
} Arena;
void *arena_alloc(Arena *a, size_t n, size_t align) {
(void)align;
if (!a || a->off + n > a->cap) {
return NULL;
}
void *p = a->base + a->off;
a->off += n;
return p;
}
프로덕션 환경에서는 정렬, 오버플로 검사, 스레드 정책까지 완성도 있게 구현해야 하며, 위 예제는 개념을 보여주기 위한 압축 샘플입니다. tcmalloc이나 jemalloc과 같은 대체 할당기를 도입하면 경합 양상과 프로파일 결과가 달라지므로, 관련 지표를 다시 측정하고 맞출 필요가 있습니다.
10. 실전: 동적 vector·문자열·Person 생성
realloc로 capacity를 늘릴 때는 곱셈으로 인한 오버플로를 항상 의심해야 합니다.
#include <stdlib.h>
#include <string.h>
#include <errno.h>
typedef struct {
int *data;
size_t size;
size_t cap;
} IntVec;
static int intvec_grow(IntVec *v, size_t need) {
if (need <= v->cap) {
return 0;
}
size_t ncap = v->cap ? v->cap : 8;
while (ncap < need) {
if (ncap > (size_t)-1 / 2) {
return ENOMEM;
}
ncap *= 2;
}
void *p = realloc(v->data, ncap * sizeof *v->data);
if (!p) {
return ENOMEM;
}
v->data = (int *)p;
v->cap = ncap;
return 0;
}
int intvec_push(IntVec *v, int x) {
if (intvec_grow(v, v->size + 1) != 0) {
return -1;
}
v->data[v->size++] = x;
return 0;
}
void intvec_free(IntVec *v) {
free(v->data);
v->data = NULL;
v->size = v->cap = 0;
}
realloc이 실패하더라도 v->data는 그대로 유지되므로, 호출하는 쪽에서 트랜잭션처럼 원상복구를 선택할 수 있습니다. 이러한 처리를 래퍼 함수에 넣어두면 실무에서 겪는 실수를 크게 줄일 수 있습니다.
asprintf를 사용할 수 있는 환경이라면, 경계를 직접 계산하는 방식보다 이를 우선적으로 사용하는 것이 안전합니다.
#define _POSIX_C_SOURCE 200809L
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
const char *a = "Hello, ";
const char *b = "world!";
char *c = NULL;
if (asprintf(&c, "%s%s", a, b) < 0) {
perror("asprintf");
return 1;
}
puts(c);
free(c);
return 0;
}
Person 구조체에서 부분 할당 실패가 발생하는 경우, 즉 strdup이 실패했을 때 free(p)를 호출하는 것을 잊지 않아야 합니다.
#include <stdlib.h>
#include <stdio.h>
typedef struct {
int id;
char *name;
} Person;
Person *person_create(int id, const char *name) {
Person *p = (Person *)malloc(sizeof *p);
if (!p) {
return NULL;
}
p->id = id;
p->name = strdup(name);
if (!p->name) {
free(p);
return NULL;
}
return p;
}
void person_destroy(Person *p) {
if (!p) {
return;
}
free(p->name);
p->name = NULL;
free(p);
}
11. RAII 흉내·AutoFree — “래퍼”가 여기에도
C에는 C++의 RAII가 없지만, 정리(cleanup) 로직을 한곳에 모아 관리할 수는 있습니다. GCC/Clang의 cleanup 속성을 사용하는 팀도 있고, goto cleanup 패턴 역시 현실적으로 널리 쓰이는 해결책입니다.
#include <stdlib.h>
typedef struct {
void *p;
} AutoFree;
static void auto_free(AutoFree *a) {
free(a->p);
a->p = NULL;
}
int main(void) {
AutoFree buf = { malloc(1024) };
if (!buf.p) {
return 1;
}
auto_free(&buf);
return 0;
}
12. 프로파일링, 멀티스레드, 임베디드
massif와 같은 도구로 어느 코드가 메모리를 많이 사용하는지, 왜 메모리가 해제되지 않는지 확인할 수 있습니다. 서버 환경에서는 힙 스냅샷의 변화 곡선을 통해 누수 여부에 대한 감을 잡을 수 있습니다.
malloc은 스레드 안전(thread-safe)하게 구현되어 있어야 하지만, 내부적으로 경합이 발생할 수 있습니다. 누가 할당한 객체를 누가 사용하는지를 명확히 정하지 않으면 레이스 컨디션, UAF, 이중 free가 한꺼번에 늘어날 수 있습니다. 임베디드 환경에서는 malloc 사용 자체를 금지하는 경우도 있으며, 이때는 정적 풀이나 슬랩 방식이 대안이 됩니다.
13. “증상”을 스토리로: 트러블슈팅 (표 없음)
free(): invalid pointer 오류를 처음 마주했던 사례가 있습니다. 원인은 포인터가 힙이 아니라 스택을 가리키고 있었기 때문이었습니다. ASan으로 최초 손상 지점을 찾아낸 뒤, safe_free와 소유권 문서화를 통해 문제를 정리했습니다.
세그폴트가 “가끔” 발생한다면 UAF, OOB, 정렬 문제, 경합을 의심해야 합니다. 먼저 ASan으로 확인하고, 경합이 의심된다면 ThreadSanitizer로 시야를 넓힙니다. “메모리는 줄지 않는데 서버는 계속 무거워진다”는 증상은 캐시 누수, 풀의 무한 성장, 핸들 누수를 의심할 수 있으며, 힙 프로파일과 트랜잭션 경계, 환경에 따라 malloc_stats를 활용해 어느 경로가 메모리를 소모하는지 추적합니다.
malloc이 부하 상황에서만 실패한다면 단편화, 주소 공간 한도, 32비트 주소 공간을 의심해야 합니다. 아레나 도입, 64비트 전환, 할당기 교체, 요청 경량화 등이 대응 방안이 될 수 있습니다. 데이터가 이상하게 손상된다면 realloc으로 블록이 이동한 뒤 기존 포인터를 계속 사용하고 있지는 않은지, 구조체 내부의 자기 참조(self) 포인터까지 확인해야 합니다.
SIMD 연산이 깨진다면 malloc의 정렬 가정을 다시 점검하고, 필요하다면 aligned_alloc이나 posix_memalign을 사용해야 합니다. VLA는 힙으로 옮기고 크기에 상한을 두어, 스택이 넘치기 전에 미리 방지해야 합니다.
14. “처리 파이프라인” — 기억용 한 장
할당·초기화·이동·공유·해제는 하나로 이어지는 사슬입니다. 이 흐름은 코드 한 줄에서 끝나지 않습니다.
flowchart LR A[필요 크기·수명·공유 정책 정하기] --> B[할당 API 선택: malloc/calloc/realloc/typed pool] B --> C[실패·오버플로·정렬 처리] C --> D[초기화·불변식 설정] D --> E[이동/별칭/스레딩·동기화] E --> F[해제: 단일 지점, 이중 free 방지]
15. CI·프로덕션에 묻는 질문 (표 대신)
PR에서 ASan이나 Valgrind가 경고를 띄울 수 있는 구조인지는 반드시 확인해야 할 항목입니다. realloc에 임시 포인터를 사용하는 것이 팀의 규칙으로 정해져 있습니까? calloc의 곱셈 오버플로 처리와, asprintf와 같은 API를 사용할 경우 이식성 fallback이 준비되어 있습니까? 고부하 경로에는 아레나·풀·대체 할당기에 대한 실측 데이터가 있습니까? 그렇지 않다면 “나중에 하겠다”는 결정이 훗날 느려진 서버로 되돌아올 수 있습니다. 이는 책상에서 미리 던져야 할 질문이며, 그렇지 않으면 온콜 근무 중 한밤중에 다시 떠오르게 될 질문입니다.
16. 요약
동적 메모리는 “주소 하나”가 아니라 할당기의 상태와 수명·별칭·스레딩이 얽힌 문제입니다. 누수는 시스템을 서서히 무너뜨리고, UAF와 이중 free는 한참 뒤에 문제를 드러냅니다. malloc을 코드 곳곳에 직접 흩어놓는 대신 팀의 래퍼 함수로 모으고, AddressSanitizer와 Valgrind로 가설을 검증하는 습관을 들이시기 바랍니다.
자주 묻는 질문 (FAQ)
Q. calloc이 malloc+memset보다 안전한가요?
A. 0 초기화가 요구사항이라면 calloc이 의도를 분명히 드러내며, 구현이 곱셈에서 실패를 조기에 잡는 이점이 있을 수 있습니다. 그러나 기밀 데이터 보호를 대체하거나, 경쟁적 공격 지점을 단독으로 막는다고 보기는 어렵습니다.
Q. 커스텀 malloc 풀은 언제 쓰나요?
A. 소량 크기·고빈도 할당, 실시간성, 단편화 완화가 요구될 때입니다. 대신 스레드 안전과 정렬과 관측 도구 호환을 설계에 포함해야 합니다.
Q. 이 글의 예제를 실무에 바로 써도 되나요?
A. 교육용 최소 예제이므로(에러 정책, 정렬, OS별 이식) 그대로 복붙하기보다 팀의 코딩 가이드에 맞게 보강하십시오.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C 언어 시리즈 #06 — 배열·디케이(decay)·문자열 리터럴·VLA
- C 언어 시리즈 #02 — 타입·승격(usual arithmetic)·정수 표현과 패딩
- C 언어 시리즈 #07 — 구조체·공용체·비트필드·패딩과 ABI
이 글에서 다루는 키워드 (관련 검색어)
C, malloc, 메모리 관리, AddressSanitizer, 아레나 할당 등으로 검색하시면 이 글이 도움이 됩니다.