<p>mmap读大文件必须手动对齐offset,因其要求offset为系统页大小(如4096)整数倍,否则返回MAP_FAILED;需用sysconf(_SC_PAGESIZE)获取页大小,按aligned_offset = offset & ~(page_size - 1)对齐,并扩展映射长度以覆盖实际需求。</p>

mmap 读大文件时为什么必须手动对齐 offset
因为 mmap 的 offset 参数要求是系统页大小(通常是 4096)的整数倍,否则会直接返回 MAP_FAILED 并设 errno = EINVAL。这不是 C++ 标准库限制,而是内核 mmap 系统调用的硬性要求。
常见错误是直接把文件偏移传给 mmap,比如想从第 12345 字节开始映射——这几乎必然失败。
- 用
sysconf(_SC_PAGESIZE)或getpagesize()获取真实页大小(别硬写 4096) - 对齐公式:
aligned_offset = offset & ~(page_size - 1)(前提是page_size是 2 的幂) - 记住:对齐后实际映射起点会「回退」,你需要额外记录逻辑起始位置偏移(例如
offset % page_size)
std::ifstream 读大文件时根本不用管页对齐
std::ifstream 是用户态缓冲 I/O,底层走 read() 系统调用,不涉及内存映射,所以完全不受页对齐约束。你传任意 seekg(n) 都合法,哪怕 n=1。
但注意:它不等于“没开销”。频繁小 seek + 小 read 会导致大量系统调用和内核上下文切换,性能远低于合理分块的 mmap 或预分配 buffer 的 read()。
立即学习“C++免费学习笔记(深入)”;
- 如果只是顺序读、单次处理几百 MB 以内,
std::ifstream简单够用,别强行上mmap - 若需随机访问 GB 级文件的任意位置,且延迟敏感,才值得引入
mmap并处理对齐 -
std::ifstream::rdbuf()->pubsetbuf()不能绕过内核缓冲,也不能控制物理页行为
munmap 后指针立即失效,但不会触发 SIGSEGV?
调用 munmap() 后,对应虚拟地址范围被内核解除映射,再次解引用该指针属于未定义行为。多数情况下不会立刻 crash,是因为:
- 内核只回收 VMA(virtual memory area),不立即清空 TLB 或刷页表项
- 若该页尚未被其他进程/线程复用,CPU 可能仍命中旧页表,读到脏数据或旧内容
- 真正出问题往往在后续内存分配重用了同一 VA,或触发缺页异常时才发现映射已不存在
所以不要依赖“没崩就是还能用”,所有 mmap 返回的指针必须严格配对 munmap,且之后彻底置为 nullptr。
使用 mmap 处理非对齐大文件的最小安全模板
以下逻辑覆盖 offset 对齐、长度扩展、指针偏移计算三个关键点:
int fd = open("huge.bin", O_RDONLY);
off_t offset = 123456789; // 想读的位置
size_t len = 8192; // 实际要读的字节数
<p>long page_size = sysconf(_SC_PAGESIZE);
off_t aligned_offset = offset & ~(page_size - 1);
size_t map_len = (offset - aligned_offset) + len;</p><p>void<em> addr = mmap(nullptr, map_len, PROT_READ, MAP_PRIVATE, fd, aligned_offset);
if (addr == MAP_FAILED) { /</em> handle error */ }</p><p>char<em> data_start = static_cast<char</em>>(addr) + (offset - aligned_offset);
// data_start 即你真正需要的起始地址,可用作普通 char*
// ... 使用 data_start ...
munmap(addr, map_len);
close(fd);</p>这里容易漏掉的是 map_len 必须包含「对齐回退距离 + 实际长度」,否则末尾可能读越界或截断。
页对齐不是玄学,是 mmap 的契约;绕不开,但也不用自己手写页表操作——把 offset 和 length 按规则掰直,剩下的交给内核。



















