堆内存管理:malloc/free原理、堆内存分配策略、常见堆实现
大家好,我是你们的嵌入式安全讲师。今天我们来聊聊堆内存管理。说实话,堆这块是很多嵌入式工程师的“噩梦”——内存泄漏、碎片化、野指针,我当年刚入行时也被折腾得够呛。但搞懂 malloc 和 free 背后的原理,很多问题就迎刃而解了。
一、malloc/free 到底干了什么?
很多人以为 malloc 就是“向操作系统要一块内存”,free 就是“还回去”。其实没那么简单。在嵌入式裸机或 RTOS 环境下,堆管理是由一个叫做“堆管理器”的模块来完成的。malloc 和 free 只是它的两个接口。
我个人习惯把堆管理器想象成一个“仓库管理员”。你 malloc(100),管理员就去仓库里找一块至少 100 字节的空闲区域,划给你用,然后在账本上记一笔。你 free(ptr),管理员就把这块区域重新标记为“空闲”,并尝试跟旁边的空闲区域合并。
二、堆内存分配策略
堆管理器怎么找空闲块?不同的策略,性能差异很大。我遇到过一些项目,因为选错了策略,导致系统运行几天后突然卡死——堆碎片化太严重了。
1. 首次适应(First Fit)
从堆头开始找,找到第一个大小足够的空闲块就分配。优点是速度快,缺点是容易把大块切碎,产生很多小碎片。
2. 最佳适应(Best Fit)
遍历所有空闲块,找到大小最接近请求的那个。碎片最少,但遍历开销大。我记得有一次调试一个音频处理模块,用 Best Fit 后性能下降明显,后来换成了 Next Fit 才解决。
3. 最差适应(Worst Fit)
找最大的空闲块,切出一块给你。目的是保留中等大小的块,但实际效果一般,容易把大块浪费掉。
4. 快速适应(Fast Fit / Segregated Fit)
把空闲块按大小分类,比如 16 字节、32 字节、64 字节……各自维护一个链表。分配时直接去对应大小的链表里取。速度极快,但内存利用率稍低。很多商业 RTOS 的堆实现都用了这个思路。
| 策略 | 分配速度 | 碎片程度 | 适用场景 |
|---|---|---|---|
| First Fit | 快 | 高 | 小内存、频繁分配 |
| Best Fit | 慢 | 低 | 大内存、对碎片敏感 |
| Worst Fit | 中等 | 中等 | 不常用 |
| Fast Fit | 极快 | 中等 | 实时系统、固定大小分配 |
三、常见堆实现:dlmalloc 与 tlsf
讲完策略,我们看看两个经典的堆实现。这两个我在 NXP 芯片上都实际用过,各有千秋。
1. dlmalloc(Doug Lea's malloc)
这是 Linux glibc 早期版本的默认 malloc 实现。它使用边界标记法(Boundary Tag),每个内存块的头尾都保存了大小信息。free 的时候可以快速合并相邻空闲块。
dlmalloc 的优点是通用性强,代码成熟。但它的实时性不好——最坏情况下的执行时间不可预测。我曾经在一个 NXP i.MX RT 系列的项目里用 dlmalloc,结果发现中断服务程序里调用 malloc 有时会卡几十微秒,直接导致音频丢帧。后来我换成了 tlsf。
2. tlsf(Two-Level Segregated Fit)
tlsf 是专门为实时系统设计的堆分配器。它的核心思想是用两级位图来管理空闲块:第一级按 2 的幂次分桶(比如 16-31、32-63...),第二级在每个桶内再细分成 4 等份。分配时,通过位图运算直接定位到合适的空闲块,时间复杂度是 O(1)。
tlsf 的实时性极好,最坏情况下的执行时间也是常数。我在 NXP S32K 系列的车规芯片上用过 tlsf,配合 FreeRTOS,内存分配延迟稳定在 1 微秒以内。嗯,这里要注意:tlsf 的内存开销比 dlmalloc 大一些,因为它需要维护位图和二级链表。
四、知识体系结构图
下面我用一张 SVG 图来总结本章的核心逻辑。你想想看,堆管理其实就三件事:怎么找、怎么分、怎么合。
五、实战建议
最后,我给大家几个实战中的小建议:
- 尽量静态分配:在嵌入式系统中,能用全局数组或静态变量解决的问题,就别用 malloc。堆是最后的手段。
- 配对检查:每次 malloc 都要有对应的 free,而且要在同一个层级。我见过太多“在函数 A 里 malloc,在函数 B 里 free”的代码,最后内存泄漏得一塌糊涂。
- 监控堆使用量:在 NXP 芯片上,你可以定期调用
mallinfo()或自定义的堆统计函数,看看还有多少空闲内存。如果空闲内存持续下降,说明有泄漏。 - 考虑内存保护单元(MPU):如果芯片支持 MPU,可以把堆区域设置为不可执行,防止堆溢出攻击。这个我们在后面的安全章节会详细讲。
好了,堆内存管理就讲到这里。记住:理解原理比背代码更重要。你搞懂了 malloc 和 free 是怎么找块、怎么切块、怎么合并的,遇到问题就能快速定位。下一章我们会讲内存泄漏的检测方法,到时候这些知识都会用上。
公众号:蓝海资料掘金营,微信deep3321