最稳做法是fread+memcmp逐块比对:先用fstat/_stat校验文件大小,再以"rb"模式读取固定缓冲区(如65536字节),严格检查fread返回值,确保每次读取一致后才memcmp,避免EOF误判或CRLF干扰。

用 fread + memcmp 逐块比对最稳
直接读文件进内存再比,是绝大多数场景下最可控、最不容易翻车的做法。它不依赖系统 API 差异,也不怕小文件或超大文件的边界问题。
常见错误现象:memcmp 返回 0 却误判为“相同”,其实是没读完就停了(比如 fread 实际读取字节数少于预期,但没检查返回值);或者读到 EOF 后还多比了一次。
- 必须检查
fread的返回值,确保每次读满缓冲区(最后一次允许不满) - 缓冲区大小建议设为 4096 或 65536 —— 太小频繁调用开销大,太大无意义且可能分配失败
- 两个文件长度不同,应先用
fstat或_stat对比 size,不等直接返回 false - Windows 下注意以
"rb"模式打开,避免 CRLF 转换干扰二进制比较
size_t buf_size = 65536;
std::vector<char> buf1(buf_size), buf2(buf_size);
while (true) {
size_t r1 = fread(buf1.data(), 1, buf_size, f1);
size_t r2 = fread(buf2.data(), 1, buf_size, f2);
if (r1 != r2) return false;
if (r1 == 0) break; // 都读完了
if (memcmp(buf1.data(), buf2.data(), r1) != 0) return false;
}
</char>
Windows 上用 CreateFileMapping + MapViewOfFile 不一定更快
文件映射听起来很酷,但实际用于“比较是否相同”时,容易高估它的优势。它本质是让 OS 把文件页按需加载进虚拟内存,而 memcmp 一访问就会触发缺页中断 —— 这和顺序 fread 的 IO 模式差不多,甚至更重。
使用场景:只在你 already 在用内存映射做其他事(比如解析/修改),顺手加个比对才划算;否则纯为比对引入映射,得不偿失。
- 必须用
SEC_COMMIT | PAGE_READONLY,别用SEC_IMAGE(那是给 PE 文件的) -
MapViewOfFile返回NULL很常见 —— 文件太大、地址空间碎片、权限不足都会导致失败,不能假设成功 - 64 位程序映射 >4GB 文件没问题,但 32 位下
MapViewOfFile最多映射约 2GB(受用户态地址空间限制) - 映射后仍要自己处理文件长度不一致的情况,
memcmp不能跨映射边界乱比
std::equal 和 std::mismatch 在 C++ 中不是好选择
有人想用 STL 算法图省事,但它们默认按 char 迭代器走,每次访问一个字节 —— 对缓存极不友好,实测比 memcmp 慢 3–5 倍,尤其在大文件上。
性能影响:现代 CPU 对 memcmp 有高度优化(SIMD、分支预测),而 std::equal 是通用模板,编译器很难替你向量化。
- 别写
std::equal(first1, last1, first2)去比整个文件 - 即使包装成函数,只要底层没用
memcmp或等效汇编,就是自降性能 - 如果非要用 STL,至少确保传入的是
const char*和长度,让编译器有机会内联memcmp
Linux/macOS 下注意 mmap 的 MAP_PRIVATE 陷阱
用 mmap 映射只读文件时,若用了 MAP_PRIVATE,某些内核版本(特别是旧版 macOS)会偷偷 copy-on-write,导致第一次访问就分配物理内存 —— 看似映射成功,实际把 swap 跑满了。
正确做法是明确用 MAP_PRIVATE | MAP_POPULATE(Linux)或 MAP_NOCACHE(macOS),但更干脆的是:别映射,老实用 read() + memcmp。
-
MAP_POPULATE可预读,但会阻塞直到所有页加载完成,对超大文件响应变差 -
mmap失败返回MAP_FAILED,不是NULL,检查时别写错 - munmap 后指针立刻失效,别拿它继续
memcmp
比对逻辑本身很简单,真正容易被忽略的是:**文件长度不等时尽早退出、每次 IO 都检查实际字节数、跨平台打开模式必须二进制**。这几个点漏掉一个,结果就不可靠。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











