跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

C++实现简单的内存碎片统计 _ 空闲链表遍历算法【源码】

结论:用自定义std::list(按地址升序维护)+ 固定大小内存池即可模拟空闲链表并统计碎片;外部碎片通过遍历相邻空闲块计算地址间隙,内部碎片统计拆分后不可用的残余块(size < MIN_ALLOC_SIZE)。 怎么用 C++ 模拟空闲链表并统计内存碎片 直接说结论:不用重写 malloc,用自定义
std::list
或结构体链表 + 固定大小内存池即可完成碎片统计。核心是把“空闲块”建模为带
addr
和
size
的节点,按地址排序后遍历,计算相邻块之间的间隙(即外部碎片)和单个块内无法分配的剩余空间(即内部碎片)。 常见错误是把“碎片”等同于“小块数量多”——实际要看能否拼成目标请求尺寸。比如有 10 个 64B 空闲块,但你要分配 512B,它们之间地址不连续,就仍是碎片。 初始化一个大数组模拟堆,如
char heap[1024 * 1024]
(1MB) 维护一个
std::list
,其中
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
。 立即学习 “ C++免费学习笔记(深入) ”; C知道 CSDN推出的一款AI技术问答工具 下载 遍历排序后的空闲块列表,对每对相邻节点
a
和
b
,计算
gap = b.addr - (a.addr + a.size)
若
gap > 0
,说明存在外部碎片,累计到总 gap 字节数 可额外统计
gap < 16
的微小间隙数量(这类几乎无法用于任何分配) 注意:最后一个块之后、第一个块之前不计入 gap(不属于堆内可用范围) 内部碎片统计不能只看 malloc 对齐 内部碎片是已分配块中未被使用的部分,常源于对齐要求或最小分配单元。但在自定义内存池中,它更常来自“拆分空闲块时的向下取整”或“用户请求尺寸与块大小不匹配”。 比如空闲块 1024B,用户请求 1000B,你按需分配后剩下 24B —— 这 24B 若小于最小分配粒度(如 16B),就成内部碎片。它仍属于空闲链表,但后续可能无法被利用。 每次从空闲块
f
中分配
req_size
时,记录
f.size - req_size
为潜在内部碎片 但真正计入统计的,是拆分后新加入空闲链表的那部分“残余块”,且其
size < MIN_ALLOC_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*
——某些平台下行为未定义。

相关文章