mmap与CreateFileMapping不能混用的根本原因是语义不一致:Linux支持稀疏映射、按需调页和匿名映射,而Windows要求显式指定大小且对未写入区域行为更严格,直接封装会导致崩溃或读取垃圾数据。

为什么 mmap 和 CreateFileMapping 不能直接混用
跨平台内存映射的核心矛盾不是“怎么写”,而是“语义不一致”:Linux 的 mmap 默认支持稀疏映射、按需调页、匿名映射;Windows 的 CreateFileMapping + MapViewOfFile 要求显式指定大小,且对文件末尾未写入区域的行为更严格。直接封装一层函数名映射(比如把 mmap 简单替换成 CreateFileMapping)会导致在 Windows 上打开空文件或截断文件时崩溃,或读到未初始化的垃圾数据。
关键差异点:
-
mmap的length可大于文件当前大小(内核自动补零);CreateFileMapping的dwMaximumSizeHigh/dwMaximumSizeLow必须 ≥ 文件实际大小,否则MapViewOfFile失败并返回NULL - Linux 下
MAP_SHARED修改会同步回磁盘;Windows 下必须调用FlushViewOfFile+FlushFileBuffers才能保证落盘 - Windows 不支持
MAP_ANONYMOUS,得用INVALID_HANDLE_VALUE+SEC_COMMIT模拟,但此时无法跨进程共享
如何设计最小可行的跨平台接口
不要试图抽象出“完全一致”的 API,而是聚焦高频场景:只读映射、读写映射、固定大小文件(如配置/资源文件)。接口应只暴露三个核心操作:open、map、unmap,其余细节下沉到实现。
推荐结构:
立即学习“C++免费学习笔记(深入)”;
class MemoryMappedFile {
public:
enum class Access { READ_ONLY, READ_WRITE };
explicit MemoryMappedFile(const std::string& path, Access access = Access::READ_ONLY);
~MemoryMappedFile();
<pre class='brush:php;toolbar:false;'>bool map(); // 映射成功返回 true
void unmap();
void* data() const { return data_; }
size_t size() const { return size_; }private: std::string path; Access access; void* data_ = nullptr; sizet size = 0;
ifdef _WIN32
HANDLE file_handle_ = INVALID_HANDLE_VALUE; HANDLE mapping_handle_ = nullptr;
else
int fd_ = -1;
endif
};
注意:map() 必须检查文件是否真实存在且可访问,Windows 下尤其要先用 CreateFile 获取句柄并验证 GetFileSizeEx,避免后续 CreateFileMapping 因文件大小为 0 或权限不足而静默失败。
Windows 下必须补的两个关键步骤
很多跨平台封装在 Windows 上读写后数据不落盘,不是因为代码错,而是漏掉了两步系统级同步。
- 调用
MapViewOfFile后,若为READ_WRITE模式,必须在unmap()前显式调用FlushViewOfFile(data_, size_) - 仅
FlushViewOfFile不够 —— 它只刷内存视图到系统缓存,还需对原始文件句柄调用FlushFileBuffers(file_handle_),否则断电或强制关机时可能丢失 - 如果映射的是空文件(size == 0),Windows 要求先用
SetFilePointerEx+SetEndOfFile扩展文件,否则CreateFileMapping返回ERROR_INVALID_PARAMETER
示例片段(Windows map() 关键逻辑):
if (access_ == Access::READ_WRITE) {
LARGE_INTEGER li;
li.QuadPart = static_cast<LONGLONG>(size_);
if (!SetFilePointerEx(file_handle_, li, nullptr, FILE_BEGIN) ||
!SetEndOfFile(file_handle_)) {
return false;
}
}
mapping_handle_ = CreateFileMapping(file_handle_, nullptr,
access_ == Access::READ_ONLY ? PAGE_READONLY : PAGE_READWRITE,
0, static_cast<DWORD>(size_), nullptr);Linux 和 macOS 下容易忽略的错误检查
Linux 的 mmap 表面简单,但几个返回值和 errno 组合极易被忽略:
-
mmap返回MAP_FAILED(即(void*)-1),不是nullptr—— 直接判== nullptr会漏掉错误 - 常见 errno:
EINVAL(offset 非页对齐、length 为 0)、ENODEV(文件系统不支持 mmap,如某些网络挂载)、EPERM(noexec mount 选项下尝试执行映射) - macOS 对
MAP_JIT有额外要求(仅限 Apple Silicon 上 JIT 场景),普通用途不用加,但若误加会导致mmap失败且 errno 为ENOTSUP - 映射后建议立即用
msync(data_, size_, MS_ASYNC)测试是否可写(尤其在容器或某些虚拟化环境中),避免运行时才暴露权限问题
真正麻烦的从来不是写一次跨平台代码,而是不同平台对“文件末尾”“权限变更”“挂载类型”的容忍度差异 —— 这些边界情况往往只在特定部署环境里爆发。


















