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