单独用std::queue实现fifo页面置换命中率一定算错,因其无find()、不支持o(1)查找,遍历判断退化为o(m),且残留淘汰页号导致重复缺页;必须配合std::unordered_set同步维护驻留状态,二者增删查操作严格原子对齐,命中率分母恒为总访问次数n。

单独用 std::queue 实现 FIFO 页面置换,命中率一定算错——不是精度问题,是逻辑崩坏。
为什么 std::queue 单独查不了“页是否在内存”
它没有 find()、不支持随机访问、也不能遍历;每次判断命中都得手动遍历整个队列,时间复杂度从 O(1) 退化成 O(m),m 是物理块数。更致命的是:队列里可能残留已被淘汰的页号(比如刚 pop 过但没同步清理状态),导致重复访问仍被误判为缺页。
- 错误写法:
if (q.find(page) != q.end())→ 编译失败,std::queue根本没有find成员函数 - 退化写法:
for (auto x : q) if (x == page) → hit→ 每次访问都要扫一遍,m=64 时单次就多 64 次比较 - 真实后果:同一页面第二次访问仍触发缺页,
pageFaults虚高,命中率被系统性压低
std::queue 必须配 std::unordered_set 才能跑通
前者管“谁该被淘汰”,后者管“谁还在内存”,二者更新必须原子对齐——装入、淘汰、查询三步操作中,漏掉任意一次 insert() 或 erase(),状态就立即失真。
- 装入新页时:
q.push(page)✅ +in_memory.insert(page)✅(缺一不可) - 淘汰旧页时:
int old = q.front(); q.pop();✅ +in_memory.erase(old)✅(顺序不能反) - 查命中时:只用
in_memory.find(page) != in_memory.end(),完全不动q - 边界检查不能少:
if (!q.empty() && q.size() == frame_count),否则q.front()在空队列上调用是未定义行为
循环数组手写 FIFO 时 cur 下标怎么不出界
手写比 STL 更容易暴露底层陷阱:cur 不是计数器,而是“下次该淘汰位置”的索引,它只在缺页且内存已满时才更新,且必须严格绑定物理块数 m。
- 数组大小必须是
new int[frame_count],不能是new int[100]后只用前frame_count个——越界写入会覆盖相邻变量 -
cur = (cur + 1) % frame_count是安全写法,但前提是cur初始为 0 且只在淘汰发生时执行 - 内存未满阶段(前
frame_count次装入)绝不能动cur,否则最老页位置漂移,首次淘汰就错 - 如果
frame_count == 1,(cur + 1) % 1恒为 0,逻辑正确;但若cur初始化为 -1,负数取模行为依赖编译器,建议显式初始化为 0
命中率分母为什么必须是总访问次数
操作系统定义明确:缺页率 = 缺页次数 / 总页面访问次数。这个“总次数”就是输入序列长度 n,一个都不能少,包括第一次访问、填满前所有访问、甚至重复页的首次与后续访问。
- 示例序列
7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1共 17 次 → 分母必须是 17 - 误把前 3 次(填满驻留集)跳过统计 → 分母变小,命中率人为抬高
- 把重复页去重再计数 → 分母缩水,在短周期序列(如
0 1 2 0 1 2)中误差爆炸 - 输出写成
hit / (n - pageFaults)→ 数学定义错误,不是“命中 / 非缺页”,而是“命中 / 总访问”
最容易被忽略的点,其实是“首次装入也算一次缺页中断”——FIFO 的调度意义,就始于那第一次 pageFault,而不是等内存满了才开始计数。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











