Heap
目录
堆的概念
堆是程序运行时动态申请内存的地方,就像一个大仓库,可以随时借地方存放数据,用完后需要手动归还。
示意图:
┌─────────────────┐
│ 堆空间 │
│ │
│ [空闲区域] │
│ [已使用块] │
│ [空闲区域] │
│ [已使用块] │
└─────────────────┘
堆与栈的区别
内存布局对比:
┌─────────────────┐
│ 栈 │ ← 自动管理,像整齐书架
│ 后进先出 │ 编译器自动分配释放
├─────────────────┤
│ 堆 │ ← 手动管理,像杂货仓库
│ 随意存取 │ 需要自己申请和释放
└─────────────────┘
栈的特点: - 自动分配和释放 - 大小固定 - 访问速度快
堆的特点: - 手动申请和释放 - 空间灵活 - 访问速度较慢
基本堆管理机制
空闲块链表
系统维护一个空闲内存块的链表,申请内存时从这个链表中查找合适的块。
示意图:
空闲链表:头 → [块A:32字节] → [块B:16字节] → [块C:24字节] → NULL
堆内存:
[已用][块A-空闲][已用][块B-空闲][已用][块C-空闲]
堆内存分配和释放的过程
内存分配过程
当程序申请内存时,分配器在空闲链表中寻找足够大的块。
分配步骤: 1. 在空闲链表中搜索合适大小的块 2. 找到后,从该块中分割出需要的大小 3. 剩余部分形成新的空闲块 4. 更新空闲链表
分配示意图:
初始状态:
空闲链表:[块A:32字节] → [块B:16字节]
申请20字节:
1. 找到块A(32字节)
2. 分割:20字节(已用) + 12字节(新空闲块)
3. 结果:[已用20B] [空闲12B] [空闲16B]
内存释放过程
当程序释放内存时,需要将内存块重新加入空闲链表。
释放步骤: 1. 将释放的块标记为空闲 2. 检查相邻块是否也是空闲 3. 如果相邻空闲,进行合并操作 4. 将合并后的块加入空闲链表
释放示意图:
初始状态:
[已用A][空闲B][已用C][空闲D]
释放块A:
[空闲A][空闲B][已用C][空闲D]
合并A和B:
[大空闲AB][已用C][空闲D]
内存碎片化的过程
碎片化如何产生
随着多次分配和释放,内存逐渐被分割成许多小块。
碎片化过程示意图:
初始: [大空闲块128字节]
分配32B: [已用32][空闲96]
分配24B: [已用32][已用24][空闲72]
分配16B: [已用32][已用24][已用16][空闲56]
释放24B: [已用32][空闲24][已用16][空闲56]
分配20B: [已用32][已用20][碎片4][已用16][空闲56]
现在有:2个空闲块(4B + 56B),但无法分配60B的请求
内存碎片问题
内部碎片
分配的内存比实际需要的大,多出来的部分被浪费。
示意图:
申请10字节,分配16字节:
[块头|10字节数据|6字节浪费]
↑
内部碎片
外部碎片
空闲内存被分割成很多小块,无法满足大内存申请。
示意图:
堆状态:
[已用][空闲8B][已用][空闲12B][已用][空闲6B]
申请20字节:失败!
总空闲=26字节,但都不连续
常见分配算法
首次适应算法
从链表开头开始查找,找到第一个足够大的块就分配。
工作过程:
空闲链表:[25B] → [18B] → [32B] → [12B]
申请20字节:
检查25B → 足够大,分配
结果:[已用20B|碎片5B] → [18B] → [32B] → [12B]
最佳适应算法
查找与申请大小最接近的空闲块,减少浪费。
工作过程:
空闲块:8B, 25B, 15B, 30B
申请12字节:
找到最匹配的15B块
结果:8B, [已用12B|碎片3B], 30B
RTOS中的堆管理
单一堆管理
整个系统共用一个堆空间,所有任务都从这里申请内存。
示意图:
任务A 任务B 任务C
↓ ↓ ↓
┌───────────────┐
│ 系统堆 │
└───────────────┘
固定大小内存池
预分配很多相同大小的内存块,分配时直接给出一个空闲块。
示意图:
内存池:
[块1][块2][块3][块4][块5][块6]
│ │ │ │ │ │
32B 32B 32B 32B 32B 32B
优点:分配速度快,无外部碎片
缺点:可能内部碎片
常见错误类型
内存泄漏
申请内存后忘记释放,导致可用内存越来越少。
示意图:
申请 → [内存块] → 忘记释放
申请 → [内存块] → 忘记释放
...
最终:内存耗尽
使用已释放内存
释放内存后继续使用,导致数据损坏或程序崩溃。
示意图:
释放 → [内存块] ✅
使用 → [同一内存块] ❌ 危险!
重复释放
对同一块内存释放两次,通常导致程序崩溃。
示意图:
释放 → [块A] ✅
再次释放 → [块A] ❌ 程序崩溃