C 语言内存分配器原理
更新时间:2026-08-26。本文是
languages/c/主题专家层文档。你天天malloc/free,但你知道背后发生了什么吗?小分配和大分配走的是两条不同的路,realloc可能悄悄"搬家",频繁分配会成为性能瓶颈。本文用实测数据揭开 malloc 的内部机制。
本文要回答的问题
malloc(100)真的只分 100 字节吗?头部开销有多少?- 大分配和小分配走的路为什么不一样?brk 和 mmap 是什么?
realloc为什么可能"搬家"?内存碎片是怎么来的?
一、实测:malloc 的秘密
c
char *p1 = malloc(100);
char *p2 = malloc(100);
printf("实际可用: %zu, 间隔: %td\n", malloc_usable_size(p1), p2 - p1);
void *big = malloc(10 * 1024 * 1024);
printf("大分配 10MB 可用: %zu\n", malloc_usable_size(big));实测:
text
小分配 100 字节,实际可用: 104, 两个指针间隔: 112
大分配 10MB 可用: 10489840发现 1:malloc(100) 实际给 104 字节——多出来的 4 字节(实际是 8/16 字节头部)是 malloc 用来记录"这块多大、是否空闲"的元数据。
发现 2:10MB 大分配给了 10489840 字节——远大于 10MB,因为大块走 mmap、按页(4KB)对齐。
二、brk 与 mmap:两条分配路径
malloc 内部按大小走两条路:

| 路径 | 分配方式 | 特点 |
|---|---|---|
brk | 移动堆顶指针 | 小分配、快,但碎片多、难整体归还 |
mmap | 独立内存映射 | 大分配、页对齐,free 时直接归还给内核 |
阈值通常是 128KB(M_MMAP_THRESHOLD),大于它走 mmap。
三、realloc 的"搬家"
c
char *p3 = malloc(10);
p3 = realloc(p3, 10 * 1024 * 1024);实测地址变化:
text
realloc 前地址: 0x1b440f0 ← 堆区(brk,小地址)
realloc 到 10MB 后: 0x7f490e66b010 ← mmap 区(高地址)地址彻底变了——realloc 扩展大小时,如果原地扩展不了,就"搬家":重新分配一块更大的、拷贝旧数据、释放旧的。这是 realloc 的隐性成本(有拷贝开销)。
这也解释了为什么
realloc的返回值必须重新赋值,且失败时原指针仍有效(见 动态内存管理)。
四、free 链与内存碎片
free 不立即把内存还给内核,而是放进 free 链(bins) 供后续复用:
| bin 类型 | 用途 |
|---|---|
| fastbin | 小尺寸快速复用(LIFO) |
| small bin | 小尺寸(≤512B) |
| large bin | 大尺寸 |
| unsorted bin | 刚 free 的暂存 |
频繁分配/释放不同大小,会导致内存碎片:空闲块很多但都很小,拼不出大块,即使总空闲够也分配失败。这是 malloc 的经典问题,也是自定义内存池要解决的。
五、自定义内存池与替代分配器
5.1 为什么需要内存池
- malloc 每次调用有开销(查 bin、锁竞争)
- 频繁小分配产生碎片
- 多线程下全局 malloc 有锁竞争
5.2 替代分配器
| 分配器 | 特点 |
|---|---|
| tcmalloc(Google) | 每线程缓存,无锁小分配 |
| jemalloc(Facebook) | 减少碎片、多线程优化 |
| 自定义内存池 | 定长块池,极快但需手动管理 |
c
// 简单定长内存池思路:预分配一大块,按定长切分复用
#define BLOCK 64
char pool[BLOCK * 1024];
int free_list[1024]; // 空闲块索引链表六、与本站主线衔接
- mmap 实践:malloc 大分配走 mmap,见 mmap 内存映射实践。
- 进程内存布局:brk 堆、mmap 区在地址空间的位置,见 内存布局与 ABI。
- 缓存:malloc 分配的内存布局影响缓存命中,见 缓存与 C 程序性能。
- perf 剖析:malloc 热点定位,见 perf 剖析 C 程序。
一句话总结
malloc 有头部开销(100 字节实际 104)、小分配走 brk 大分配走 mmap、realloc 可能搬家拷贝、free 进 bins 复用产生碎片;高频小分配用 tcmalloc/jemalloc 或自定义内存池,这是高性能 C 程序的必修课。