跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

C++实现内存映射文件的高效读取 _ mmap与CreateFileMapping【源码】

内存映射文件通过将文件直接映射到进程地址空间实现零拷贝高效I/O;Linux用mmap,Windows用CreateFileMapping+MapViewOfFile,二者语义相近但API、权限控制、错误处理及生命周期管理差异巨大,无法简单封装为统一接口。 Linux 用
mmap
,Windows 必须用
CreateFileMapping
+
MapViewOfFile
,二者语义接近但 API 完全不兼容,混用会编译失败或运行崩溃。 为什么不能直接跨平台写一个通用
mmap
封装? 表面上都是“把文件变成内存地址”,但底层契约差异太大:
mmap
返回的指针可直接读写,且支持
MAP_SHARED
让修改自动落盘;
MapViewOfFile
返回的指针默认只读,写入前必须调用
VirtualProtect
改权限,且需显式调用
FlushViewOfFile
才能保证数据写入文件 Linux 下
mmap
可映射 /dev/zero 或匿名页;Windows 的
CreateFileMapping
第一个参数不能为
INVALID_HANDLE_VALUE
(除非是后备存储,但行为受限) 错误处理方式不同:
mmap
失败返回
MAP_FAILED
(即
(void*)-1
);Windows API 全部靠返回值 +
GetLastError()
判断
mmap
读取大文件时,为什么加
MAP_POPULATE
? 不加的话,首次访问每页都会触发缺页中断,对 GB 级文件可能造成数百毫秒延迟抖动。加了它,内核在
mmap
返回前就预读并锁定物理页——但仅在你确定要全量读取时才用,否则浪费内存和 I/O。 典型用法: 立即学习 “ C++免费学习笔记(深入) ”; C知道 CSDN推出的一款AI技术问答工具 下载
void* addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd, 0); if (addr == MAP_FAILED) { perror("mmap"); return; }
MAP_POPULATE
在大多数现代内核(≥2.5.42)可用,但嵌入式或旧系统可能不支持,需
#ifdef __linux__
隔离 若文件远大于物理内存,
MAP_POPULATE
可能阻塞较久,甚至因 OOM 被 kill;生产环境建议配合
madvise(addr, size, MADV_WILLNEED)
替代 Windows 下
CreateFileMapping
映射失败的三个高频原因 不是权限问题就是尺寸问题,90% 的失败都落在下面这三条里: 传给
CreateFile
的
dwDesiredAccess
和
CreateFileMapping
的
flProtect
不匹配:比如文件以
GENERIC_READ
打开,却用
PAGE_WRITECOPY
创建映射 → 失败,
GetLastError()
返回
ERROR_ACCESS_DENIED
dwMaximumSizeHigh
/
dwMaximumSizeLow
设得太小:如果文件大小超过 4GB,必须用两个 DWORD 拆开传,漏设
dwMaximumSizeHigh
导致高位截断 → 映射视图只能看到前 4GB 映射后调
MapViewOfFile
时
dwNumberOfBytesToMap
超出文件实际长度:不会报错,但越界访问引发
ACCESS_VIOLATION
;务必用
GetFileSize
校验,而非依赖传入的 size 参数 读取性能关键:别让 memcpy 干扰内存映射优势 映射本质是零拷贝,一旦你用
memcpy(dst, mapped_addr, n)
把数据搬走,就退回传统 I/O 模式,还多占一份内存。真正高效的做法是直接在映射地址上解析结构体或跳过头部字段:
// 假设文件开头是 8 字节 magic + 4 字节 length uint32_t* len_ptr = (uint32_t*)((char*)mapped_addr + 8); size_t data_len = *len_ptr; const char* payload = (char*)mapped_addr + 12; // 直接用 payload,不 copy
注意对齐:x86_64 上
uint32_t*
解引用要求地址 4 字节对齐,而
mmap
返回地址只保证页面对齐(4096 字节),通常安全;但 Windows 的
MapViewOfFile
返回地址对齐性取决于
dwFileOffsetLow
,手动偏移后务必检查 不要长期持有映射指针:进程退出时系统自动清理,但若映射超大文件又长时间不访问,可能被内核 swap 出去,下次访问变慢;频繁随机访问建议搭配
madvise(MADV_WILLNEED)
或
VirtualAlloc + ReadFile
分块加载 跨平台封装最难的不是函数调用,而是错误恢复路径和生命周期管理——比如 Linux 下
munmap
后指针立刻失效,Windows 下
UnmapViewOfFile
成功后仍可能访问已释放页(取决于内存状态),这种非对称行为必须在 RAII 封装里显式对齐。

相关文章