free 并不是把进程地址空间里的那一页直接"还"给操作系统,而是在用户态的堆管理器(glibc/ptmalloc、Windows CRT、jemalloc、tcmalloc 等)里完成一次记账操作。下面以 glibc 默认的 ptmalloc2 为例,把一次 free(ptr) 的完整链路拆开说明,其他实现思路大同小异。
1、先做"合法性"检查
如果 ptr == NULL,立即返回。
把指针往回偏移一个固定长度,拿到 chunk 头(下面会讲头里有什么)。
检查该 chunk 的魔数、对齐位、是否属于当前 arena 等,若发现异常直接 abort(经典的 "free(): invalid pointer" 报错就来自这里)。
2、怎么"知道"要释放多大
malloc 返回给用户的地址 并不是 真正从系统要到的起始地址,而是 紧接在头部后面的用户区。头部里保存了本次分配的大小(以及标志位、魔数等)。
free 时把传入的指针往前挪 8~16 B 就能拿到这个 "malloc_chunk" 头,从而读出 chunk_size [⁴][⁷]。
3、放进哪个"桶"------空闲链表
ptmalloc 把空闲块按大小分两类管理:
≤ 64 B 的精确大小 → fastbin[](LIFO,单向链表,不回填中央堆)。
65 B ~ 128 KB → small/large bin(双向链表,按大小排序)。
更大 → top chunk 直接 mmap,释放时用 munmap 还给内核。
根据大小把 chunk 链到对应 bin 里,并置标志位 IS_INUSE=0
4、立即合并(backward & forward coalesce)
检查物理相邻的前后两个 chunk 是否也是空闲:
是 → 把它们从各自 bin 中摘下,合并成一个更大的 chunk,再挂到对应的大 bin 里。
否 → 直接把自己挂进去。
这一步用来缓解外部碎片
5、是否"还"给操作系统
只有当 top chunk(堆顶最后一块空闲)大小超过 128 KB 时,ptmalloc 才会通过 brk(-size) 把尾部缩减,真正归还给内核;否则仍留在进程堆内,供下次 malloc 复用 [⁵][⁶]。
因此大多数 free 只是用户态记账,不会立刻体现为系统内存下降。
6、多线程场景
每个线程默认先绑定一个 arena(内存池),free 时若发现该 chunk 属于别的 arena,会把内存"搬家"到对应 arena 的空闲链表,再加互斥锁,防止并发破坏链表

所以,free 的"释放"本质是 "把一块内存从占用状态标成空闲,并挂进分配器的空闲链表";只有堆顶出现足够大的连续空闲块时,才会通过系统调用真正缩小进程地址空间。
Q: 是怎么找到chuck_size的?

