应分块读取大文件以避免内存爆炸,使用64KB/128KB缓冲区,检查fread返回值,对齐起始位置后逐块比较,定位并输出十六进制偏移及字节级差异(如0x1A2F: 0x3C vs 0x3E),妥善处理EOF与长度不等情形,可选mmap加速但需fallback。

用 fread 逐块读取避免内存爆炸
直接把整个二进制文件 fread 进内存,对几百 MB 以上的文件就是灾难。实际工具必须分块读——常见做法是用 64KB 或 128KB 缓冲区(std::vector),每次读一块,对比一块。注意 fread 返回值可能小于请求长度(到 EOF 或 I/O 错误),必须检查,否则会把未初始化内存当有效数据比。
关键点:
- 两个
FILE*要用fseek(fp, 0, SEEK_SET)对齐起始位置再开始循环 - 用
size_t n1 = fread(buf1.data(), 1, buf1.size(), fp1)获取真实读入字节数 - 只在
n1 == n2且n1 > 0时 memcmp,否则按“一方提前结束”处理 - 记录当前偏移量(
offset += n1),用于后续定位差异位置
差异定位要输出十六进制偏移 + 字节级差异
用户真正需要的不是“不一样”,而是“哪里不一样、差成什么样”。所以不能只返回 bool,得输出类似:0x1A2F: 0x3C vs 0x3E。这意味着你得在 memcmp 失败后,遍历缓冲区找第一个不同字节索引,再换算成全局偏移(offset + i)。
实操建议:
- 不要用
std::mismatch—— 它对原始字节无意义,且不保证返回首个差异点(取决于实现) - 手写循环:for (size_t i = 0; i
- 输出格式统一用
printf("0x%zx: 0x%02x vs 0x%02x\n", diff_pos, buf1[i], buf2[i]),%02x确保单字节补零 - 一次只报前 10 个差异,加
--max-diff=参数控制,防刷屏
处理文件长度不等的边界情况
memcmp 只能发现“同位置内容不同”,但无法捕获“A 文件还有剩余字节而 B 已结束”这类差异。这是二进制比对最常被忽略的坑。
正确逻辑是:
- 当
n1 != n2时,说明至少一个文件已到 EOF;此时若n1 > 0 && n2 == 0,则从offset开始所有剩余字节都算差异(A 多出内容) - 同样,
n1 == 0 && n2 > 0表示 B 多出内容 - 必须单独处理这个分支,不能只依赖 memcmp 结果
- 输出提示如:
0x40000: A has extra 123 bytes,而不是静默跳过
用 mmap 加速大文件(Linux/macOS)但别盲目替换
mmap 对 >100MB 的文件确实更快,但代价是:Windows 没原生 mmap(得用 CreateFileMapping)、小文件反而更慢、内存占用不可控。进阶工具应该提供开关,而非默认启用。
若决定支持:
- Linux 下用
open()+mmap()+munmap(),检查MADV_RANDOM是否可用 - macOS 注意
MAP_FILE已废弃,改用MAP_ANONYMOUS配合read()预加载 - 务必 fallback 到 fread 方案:检测
mmap失败(如ENOMEM)时自动切回流式读取 - 别忘了
msync—— 虽然只读场景不需要,但代码里留个注释提醒“若未来支持写入模式需同步”
真正的难点不在 memcmp,而在如何把“字节流差异”映射成用户可操作的信息:偏移是否对齐 sector?差异是否在 header 区域?这些得靠额外元数据或用户传参(比如 --header-size=512)来辅助判断。没这层,就只是个高级 cmp 命令而已。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!









