mmap比fread更适合大文件分块读取,因其将文件直接映射至虚拟内存并按需加载页,避免反复系统调用与内核/用户态数据拷贝;而fread每次调用均需系统调用和缓冲区拷贝,随机访问不连续块时还需频繁fseek,易受磁盘寻道影响。

mmap 为什么比 fread 更适合大文件分块读取
因为 mmap 把文件直接映射进虚拟内存,内核按需加载页(lazy loading),不实际拷贝数据到用户缓冲区;而 fread 每次调用都要触发系统调用 + 数据拷贝,对 GB 级文件反复读取时开销显著。尤其当你要随机访问多个不连续块(比如跳着读第 10GB、第 20GB 的 1MB 区域),mmap 只需移动指针,fread 得反复 fseek + fread,还容易受磁盘寻道拖慢。
但注意:这不是万能方案。小文件(mmap 反而多出映射/解映射开销;Windows 上得用 CreateFileMapping + MapViewOfFile,不是 POSIX mmap;且必须处理页面对齐——偏移和长度都得是 getpagesize() 的整数倍,否则 mmap 失败并返回 MAP_FAILED。
如何正确计算 offset 和 length 才能 mmap 成功
直接传入你想要的逻辑偏移(比如从文件第 123456789 字节开始读 8192 字节)会失败。必须向上对齐到页边界:
-
offset要向下取整到页大小:用offset & ~(getpagesize() - 1)或offset - (offset % getpagesize()) - 映射长度要覆盖你真正需要的区域,还得至少包含一个完整页:即
length = (target_offset + target_size + getpagesize() - 1) & ~(getpagesize() - 1) - aligned_offset - 实际读取时,从
mapped_ptr + (target_offset - aligned_offset)开始取target_size字节
示例:想读文件 offset=123456789、size=8192,页大小为 4096:
aligned_offset = 123456789 & ~4095; // → 123456384 mapped_len = (123456789 + 8192 + 4095) & ~4095 - 123456384; // → 12288 data_ptr = mapped_addr + (123456789 - 123456384); // → mapped_addr + 405
munmap 后指针还能用吗?如何避免 SIGSEGV
不能。一旦调用 munmap,对应地址空间立刻失效,再访问就是未定义行为,大概率触发 SIGSEGV。常见错误是:
- 把
mmap返回的指针存成全局变量,跨函数多次使用,但某处提前munmap了 - 在
std::vector<uint8_t></uint8_t>构造时用mmap地址做 data 源,析构时 vector 不知道该 unmmap,导致内存泄漏或重复 unmmap - 异常抛出路径没
munmap,靠 RAII 封装才靠谱
推荐做法:用 RAII 封装,例如
struct MMapGuard {
void* addr;
size_t len;
MMapGuard(void* a, size_t l) : addr(a), len(l) {}
~MMapGuard() { if (addr != MAP_FAILED) munmap(addr, len); }
MMapGuard(const MMapGuard&) = delete;
MMapGuard& operator=(const MMapGuard&) = delete;
};
Windows 下等效实现要注意哪几个关键差异
Windows 没有 mmap,要用 CreateFileMapping + MapViewOfFile,且步骤更繁琐:
- 必须先用
CreateFile打开文件,且 flag 要含GENERIC_READ,不能只用FILE_READ_ATTRIBUTES -
CreateFileMapping的dwMaximumSizeHigh/dwMaximumSizeLow必须设为文件大小(即使只映射一部分),否则MapViewOfFile可能失败 -
MapViewOfFile的dwNumberOfBytesToMap可以小于文件大小,但dwFileOffsetLow必须是 64KB 对齐(不是页对齐!),否则返回 NULL - 解除映射必须按顺序:先
UnmapViewOfFile,再CloseHandlemapping handle,最后CloseHandlefile handle
没有 getpagesize(),可用 GetSystemInfo(&si); si.dwPageSize 获取,但对齐单位仍是 64KB —— 这点极易踩坑。
实际用起来,最麻烦的永远是跨平台对齐逻辑和异常安全。别图省事裸写 mmap,封装一层带自动对齐和 RAII 的类,比每次手动算 offset 省心得多。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











