必须同步维护queue与unordered_set以保证O(1)查找和FIFO语义;手写循环数组需严格管理cur指针;命中率统计分母恒为总访问次数;frameCount=0/1需特殊处理;内存快照应基于residentSet或frameOrder重建。

一、同步维护 queue 与 unordered_set
仅使用 std::queue 无法判断页面是否已在内存中,因其不支持 O(1) 查找;必须配合 std::unordered_set 记录当前驻留页集合,二者操作必须严格原子对齐,否则将导致命中误判、缺页虚高或淘汰错位。
1、声明两个核心结构:std::queue
2、每次访问页面前,先执行 if (residentSet.find(pageId) != residentSet.end()) 判断命中,命中则跳过后续所有置换逻辑。
3、若缺页,先检查 pageQueue.size() == frameCount,仅当内存已满才触发淘汰;否则直接插入新页。
4、淘汰时,取 int victim = pageQueue.front(); pageQueue.pop();,并立即执行 residentSet.erase(victim);。
5、插入新页时,必须同步执行 pageQueue.push(pageId); residentSet.insert(pageId);,缺一不可。
二、手写循环数组替代 queue
使用 std::vector
1、分配大小严格为物理块数 m:std::vector
2、仅在发生缺页且 frames 已满(即已填入 m 个非 -1 值) 时,才执行 cur = (cur + 1) % m;。
3、淘汰页前,先保存 int victim = frames[cur];,然后将新页写入 frames[cur],完成覆盖。
4、同步更新驻留状态:淘汰 victim 后,必须从 residentSet.erase(victim);插入新页 pageId 前,必须 residentSet.insert(pageId)。
5、首次装入阶段(内存未满),禁止修改 cur;此时新页直接写入首个空位(如遍历 frames 找 -1),且仍需 insert 到 residentSet。
三、命中率统计的分母校准
命中率计算依赖分子(缺页次数)与分母(总访问次数)的严格定义;分母若被截断或过滤,将导致结果与真实操作系统行为脱节,失去可比性。
1、初始化计数器:int pageFaults = 0;,不预设任何偏移或跳过逻辑。
2、对输入页面序列 pages(长度为 n),遍历下标 i 从 0 到 n−1,每轮均计入分母:分母恒为 pages.size()。
3、每次访问 pages[i] 时,若 residentSet.find(pages[i]) == residentSet.end(),则执行 pageFaults++ 并进入置换流程。
4、禁止跳过前 m 次访问(填满阶段):例如序列长度为 17,无论前 3 次是否填满内存,分母必须为 17。
5、禁止对页面序列去重或剔除重复访问:同一页面连续出现三次(如 0 0 0),第一次为缺页,后两次必须判定为命中并计入分母。
四、frameCount 边界值处理
物理帧数为 0 或 1 是合法输入,常用于验证算法鲁棒性;若未显式处理,将引发未定义行为或静默错误,导致命中率完全失真。
1、构造函数中检查:if (frameCount == 0) { /* 所有访问必缺页,pageQueue 与 residentSet 保持空 */ }。
2、当 frameCount == 1 时,每次新页都会覆盖唯一驻留页,pageQueue.size() 始终为 0 或 1,residentSet.size() 恒为 0 或 1。
3、访问前必须检查 !pageQueue.empty() 再调用 front(),尤其在 frameCount == 0 或极短序列下,避免未定义行为。
4、若 frameCount > pages.size(),则不会触发任何淘汰,缺页次数等于序列中不同页面的数量,可用作快速逻辑校验。
5、对 frameCount == 0 的情况,应直接返回 pageFaults = pages.size(),且不执行任何 queue 或 set 操作。
五、输出内存快照的正确方式
std::queue 不支持遍历,直接打印其内容无法反映当前内存中实际驻留的页面集合;必须通过辅助结构重建可视化的帧状态,否则输出无意义。
1、每次访问完成后,不尝试遍历 pageQueue,而是提取 std::vector
2、对 snapshot 排序(如升序)以提升可读性:std::sort(snapshot.begin(), snapshot.end());。
3、若需按入队时间顺序展示(FIFO 语义),应额外维护 std::vector
4、打印时使用 for (int x : snapshot) cout ,确保输出的是真实驻留页集合,而非队列底层存储顺序。
5、禁止在输出逻辑中调用 pageQueue.front() 或循环 pop 来“读取全部”,这将破坏队列状态并导致后续调度失败。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











