mmap读大文件比fread快,因其将文件直接映射至进程虚拟内存,省去内核/用户态数据拷贝与系统调用开销,访问即内存操作;但需注意懒映射、页对齐、错误检查及适用场景。

为什么直接用 mmap 读大文件比 fread 快?
因为 mmap 把文件逻辑地址直接映射进进程虚拟内存,后续访问就像读数组一样——不触发系统调用、无内核态/用户态切换、还能靠页缓存自动预取。但前提是文件得是普通文件、有确定大小、且你愿意用指针算术而非流式接口。
常见误区是以为只要 mmap 就一定快:如果只读几 KB,或频繁随机跳转且命中率低,反而可能因缺页中断拖慢;另外,mmap 不保证立即加载物理页,首次访问仍会阻塞。
- 必须检查
mmap返回值是否为MAP_FAILED,否则后续指针解引用会段错误 - 映射长度不能超过文件实际大小(
stat.st_size),否则读越界行为未定义 -
PROT_READ足够时别加PROT_WRITE,避免内核多分配写时复制页
如何安全地把 mmap 返回的 void* 转成结构体指针?
直接 reinterpret_cast<mystruct>(addr)</mystruct> 是常见做法,但前提是:结构体必须是标准布局(std::is_standard_layout_v<mystruct></mystruct> 为 true),且文件中二进制布局与当前平台的对齐、字节序完全一致。
例如读一个固定 64 字节头 + 后续连续 int32_t 数组的二进制日志文件:
struct LogHeader {
uint32_t version;
uint32_t entry_count;
char reserved[56];
};
// 确保没 padding:static_assert(sizeof(LogHeader) == 64);
auto header = reinterpret_cast<logheader>(mapped_addr);
auto entries = reinterpret_cast<int32_t>(mapped_addr + sizeof(LogHeader));
</int32_t></logheader>
- 务必用
static_assert校验结构体大小和字段偏移,尤其跨平台或含#pragma pack时 - 不要对映射区做
delete或free—— 必须用munmap - 若文件含浮点数或指针字段,重解释前需确认字节序(如网络字节序需
ntohl)
mmap 映射后,指针越界访问会怎样?
不会立刻崩溃,但结果不可控:可能读到相邻内存页的脏数据、触发 SEGV_MAPERR、或静默返回 0(取决于页表状态和 CPU)。Linux 下典型表现是收到 SIGSEGV,默认终止进程。
调试建议:
- 用
valgrind --tool=memcheck检测非法地址访问(注意:valgrind不支持mmap的所有 flag,部分场景会报错退出) - 映射时加
MAP_NORESERVE并配合mincore()预热关键页(减少首次访问延迟) - 对边界敏感的操作(如解析变长记录),始终用
mapped_len - current_offset >= needed_bytes做运行时检查
关闭映射前,要不要 msync?
只读场景完全不需要。只有你用 PROT_WRITE 修改了映射内容,并希望确保这些修改落盘到文件,才需要 msync(mapped_addr, len, MS_SYNC)。但要注意:MS_SYNC 是阻塞的,性能代价高;MS_ASYNC 仅提交写请求,不等落盘。
更常见的误用是:映射时用了 MAP_PRIVATE 却还调 msync——这无效,因为私有映射的修改不会写回原文件。
- 写共享映射(
MAP_SHARED)且需强持久性时,msync+fdatasync(fd)才算完整落盘 - 映射关闭前未
msync,而进程意外退出,修改会丢失 -
munmap本身不触发同步,它只是释放虚拟内存映射关系
真正容易被忽略的是文件描述符生命周期:mmap 不增加 fd 引用计数,close(fd) 后映射仍有效,但后续 ftruncate 或文件删除会影响映射行为。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











