结论:用自定义std::list(按地址升序维护)+ 固定大小内存池即可模拟空闲链表并统计碎片;外部碎片通过遍历相邻空闲块计算地址间隙,内部碎片统计拆分后不可用的残余块(size
怎么用 C++ 模拟空闲链表并统计内存碎片
直接说结论:不用重写 malloc,用自定义
std::list或结构体链表 + 固定大小内存池即可完成碎片统计。核心是把“空闲块”建模为带addr和size的节点,按地址排序后遍历,计算相邻块之间的间隙(即外部碎片)和单个块内无法分配的剩余空间(即内部碎片)。常见错误是把“碎片”等同于“小块数量多”——实际要看能否拼成目标请求尺寸。比如有 10 个 64B 空闲块,但你要分配 512B,它们之间地址不连续,就仍是碎片。
- 初始化一个大数组模拟堆,如
char heap[1024 * 1024](1MB)- 维护一个
std::list<freeblock></freeblock>,其中FreeBlock = { void* addr; size_t size; }- 首次将整个堆作为单个空闲块插入链表,
addr = heap,size = sizeof(heap)- 每次分配时从链表中查找合适块(首次适配/最佳适配),拆分后更新链表
- 释放时合并相邻空闲块(检查
addr和addr+size是否紧邻)如何识别和量化外部碎片(gap-based fragmentation)
外部碎片指空闲块之间存在的、因地址不连续而无法被利用的间隙。它不存储在空闲链表里,必须通过遍历排序后的空闲块推算。
关键点:空闲链表必须按
addr升序排列,否则 gap 无法计算。别用std::vector存然后每次 sort —— 插入/删除开销大,改用std::list+ 手动插入保持有序,或std::set<freeblock comparebyaddr></freeblock>。
C++ Code Review Master下载组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 遍历排序后的空闲块列表,对每对相邻节点
a和b,计算gap = b.addr - (a.addr + a.size)- 若
gap > 0,说明存在外部碎片,累计到总 gap 字节数- 可额外统计
gap 的微小间隙数量(这类几乎无法用于任何分配)- 注意:最后一个块之后、第一个块之前不计入 gap(不属于堆内可用范围)
内部碎片统计不能只看 malloc 对齐
内部碎片是已分配块中未被使用的部分,常源于对齐要求或最小分配单元。但在自定义内存池中,它更常来自“拆分空闲块时的向下取整”或“用户请求尺寸与块大小不匹配”。
比如空闲块 1024B,用户请求 1000B,你按需分配后剩下 24B —— 这 24B 若小于最小分配粒度(如 16B),就成内部碎片。它仍属于空闲链表,但后续可能无法被利用。
- 每次从空闲块
f中分配req_size时,记录f.size - req_size为潜在内部碎片- 但真正计入统计的,是拆分后新加入空闲链表的那部分“残余块”,且其
size (如 8 或 16)- 避免重复统计:同一空闲块多次拆分,只对最终不可用的尾部残余计数
- 不要依赖
sizeof(size_t)或alignof(max_align_t)算对齐开销——你的池子对齐策略由你控制,比如统一按 16B 对齐,则每块头部加 16B 元数据,这部分也属于内部碎片为什么 std::list 遍历比 vector 快,但合并操作容易出错
因为
std::list是双向链表,插入/删除 O(1),维持地址有序只需找到位置后 splice;而std::vector每次插入都要 memmove,O(n)。但问题出在合并逻辑上:合并两个空闲块,不仅要删节点,还要确保前后指针正确,尤其当三个块 A-B-C 全部连续时,一次只合并 A+B,B+C 就会失效。
- 释放内存时,先查前驱:是否存在块
p满足p.addr + p.size == freed_block.addr- 再查后继:是否存在块
n满足freed_block.addr + freed_block.size == n.addr- 合并顺序必须是:先和前驱合并(更新新块地址),再用新块地址判断是否能和后继合并
- 用
std::list::erase()删除节点后,迭代器立即失效,别存着下一轮用;改用std::next(it)或it++前先保存下一个- 调试时打印链表:写个
dump_freelist(),输出每个addr和size,肉眼一看就能发现断点或重叠最易忽略的是:碎片统计必须在稳定状态下做(即所有分配/释放完成之后),中间过程的临时碎片没有意义。还有,地址比较必须用
uintptr_t转换,别直接比void*——某些平台下行为未定义。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!












