你有没有遇到过写程序时突然崩溃,提示“内存不足”?明明电脑有 16G 内存,程序才用几百兆,怎么就崩了?这背后可能就和 malloc 的内存分配机制有关。
malloc 到底干了啥?
在 C 语言里,我们经常用 malloc 来动态申请内存。比如:
int *arr = (int *)malloc(100 * sizeof(int));
这条语句的意思是:从堆里拿一块能装下 100 个整数的内存。malloc 返回一个指针,指向这块内存的起始位置。但 malloc 并不是直接向操作系统要内存,它更像一个“中间商”。
内存池与堆管理
程序启动时,操作系统会划出一块叫“堆”的区域给 malloc 管理。malloc 并不每次都找系统要内存,而是先看看自己手里有没有空闲的。就像便利店进货,不会每次有人买一瓶水就去仓库补货,而是先卖库存。
当 malloc 发现当前没有合适大小的空闲块,才会通过系统调用(比如 brk 或 mmap)向操作系统申请更大的堆空间。
空闲内存怎么记录?
malloc 把释放后的内存块记在一个叫“空闲链表”的结构里。free 的时候,这块内存不会立刻还给系统,而是标记为空闲,留着下次分配。这样下次申请小内存时就能快速响应。
但这也带来问题:频繁 malloc 和 free 容易造成内存碎片。就像冰箱里塞满了各种大小的剩菜盒,虽然总空间够,但找不到一整块地方放新买的披萨。
什么时候真正归还内存?
大多数实现中,只有当高地址的空闲内存足够大,并且位于堆的末尾时,malloc 才可能把这部分通过 brk 缩减堆大小,变相“归还”给系统。否则,内存就一直被进程占着,哪怕你已经 free 了。
一个真实场景
假设你写了个服务程序,处理内网穿透的连接转发。每来一个连接,就 malloc 一块内存存会话信息,断开时 free。跑了一天,发现内存占用越来越高,top 看 RSS 都 2G 了,但实际活跃连接只有几百个。
这很可能是因为 malloc 没把内存还回去。虽然你 free 了,但内存碎片化严重,malloc 自己留着备用,操作系统却认为还在用。
如何观察 malloc 行为?
可以用 mallinfo、malloc_stats 或者结合 valgrind 观察内存使用情况。例如打印 malloc 统计信息:
malloc_stats();
或者在程序结束前调用,能看到总共申请了多少、空闲了多少、是否有大量碎片。
小块内存的特殊处理
对于很小的内存请求(比如几十字节),有些 malloc 实现(如 tcmalloc、jemalloc)会用独立的“内存池”管理,避免频繁操作堆。这类优化在高并发服务中特别有用,比如你的内网穿透工具同时处理上千隧道连接。
理解 malloc 的行为,能帮你写出更稳的后台程序。别再以为 free 了就万事大吉,内存能不能真还回去,还得看 malloc 的脸色。