如何通过mmap和指针操作高效实现内存映射文件读取?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1022个文字,预计阅读时间需要5分钟。
mmap+的核心优势不在于‘快’,而在于避免了内核态到用户态的多次数据拷贝。当你使用fread读取100MB文件时,系统可能需要反复调用read、分配缓冲区、复制数据——每次调用都有上下文切换和内存开销。而+mmap+只需一次映射,后续访问就像读取内存一样,通过页表和缺页中断实现懒加载(lazy loading)。但注意:
- 适合场景:顺序读 >64MB 的日志/二进制数据、只读且生命周期长的配置/资源文件
- 不适合场景:频繁写 + 同步要求高(
msync()开销大)、32 位程序映射超 2GB(地址空间不足) - 关键前提:文件必须已存在且有读权限;映射后不能删原文件(否则映射区域变成 SIGBUS)
如何安全调用 mmap 并转成可用指针?
直接对 mmap() 返回值做指针运算很危险——它可能返回 MAP_FAILED(即 (void*)-1),也可能是合法但不可读的地址(比如只映射了 PROT_WRITE)。必须先检查返回值,再用 static_cast 转为具体类型指针,而非 C 风格强制转换。
int fd = open("data.bin", O_RDONLY); if (fd == -1) { /* handle error */ } struct stat sb; fstat(fd, &sb); void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (addr == MAP_FAILED) { /* handle error */ } // 安全转指针:假设是 uint32_t 数组 const uint32_t* data = static_cast<const uint32_t*>(addr); // 记得用完 munmap(addr, sb.st_size); close(fd);
-
PROT_READ是底线,加PROT_WRITE不代表能写成功(取决于MAP_PRIVATE/MAP_SHARED) -
MAP_PRIVATE修改不落盘,适合只读场景;MAP_SHARED写后可被其他进程看到,但需msync()保证落盘 - 不要对
mmap()返回指针做delete或free——必须用munmap()
读取时踩得最多的三个指针越界坑
映射后拿到的是 raw memory 地址,编译器不会帮你检查数组边界。一旦越界,轻则读到脏数据,重则触发 SEGV_MAPERR(Linux)或直接 crash(Windows 上 MapViewOfFile 类似)。
- 误用
sizeof(data):data是指针,sizeof(data)永远是 8(64 位),不是文件大小——必须自己维护size_in_bytes - 整型类型不匹配:文件存的是
int16_t,却用int32_t*去读,每步偏移错 2 字节,数据全乱 - 未考虑字节序:跨平台二进制文件(如网络包 dump)必须用
ntohl()/htons()转换,不能直接解引用
推荐做法:封装一个带长度检查的访问器,例如 get_uint32_at(size_t offset),内部先断言 offset + sizeof(uint32_t) 。
立即学习“C++免费学习笔记(深入)”;
Windows 下等价实现要注意什么?
Windows 没有 mmap(),对应的是 CreateFileMapping() + MapViewOfFile()。关键差异在于:映射句柄(HANDLE)和视图地址(LPVOID)是分离的,且 MapViewOfFile() 的 dwNumberOfBytesToMap 参数不能为 0(Linux 下 mmap() 的 length=0 表示映射整个文件,Windows 必须显式传大小)。
- 必须先用
GetFileSizeEx()获取真实大小,不能依赖GetFileSize()(高位丢失) -
CreateFileMapping()的flProtect用PAGE_READONLY,对应 Linux 的PROT_READ - 映射失败时返回
nullptr,不是INVALID_HANDLE_VALUE——别混淆句柄和地址
跨平台代码建议:用宏或 RAII 封装,把 open/mmap/munmap/close 和 CreateFile/CreateFileMapping/MapViewOfFile/UnmapViewOfFile/CloseHandle 分两套实现,避免混用逻辑。
本文共计1022个文字,预计阅读时间需要5分钟。
mmap+的核心优势不在于‘快’,而在于避免了内核态到用户态的多次数据拷贝。当你使用fread读取100MB文件时,系统可能需要反复调用read、分配缓冲区、复制数据——每次调用都有上下文切换和内存开销。而+mmap+只需一次映射,后续访问就像读取内存一样,通过页表和缺页中断实现懒加载(lazy loading)。但注意:
- 适合场景:顺序读 >64MB 的日志/二进制数据、只读且生命周期长的配置/资源文件
- 不适合场景:频繁写 + 同步要求高(
msync()开销大)、32 位程序映射超 2GB(地址空间不足) - 关键前提:文件必须已存在且有读权限;映射后不能删原文件(否则映射区域变成 SIGBUS)
如何安全调用 mmap 并转成可用指针?
直接对 mmap() 返回值做指针运算很危险——它可能返回 MAP_FAILED(即 (void*)-1),也可能是合法但不可读的地址(比如只映射了 PROT_WRITE)。必须先检查返回值,再用 static_cast 转为具体类型指针,而非 C 风格强制转换。
int fd = open("data.bin", O_RDONLY); if (fd == -1) { /* handle error */ } struct stat sb; fstat(fd, &sb); void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (addr == MAP_FAILED) { /* handle error */ } // 安全转指针:假设是 uint32_t 数组 const uint32_t* data = static_cast<const uint32_t*>(addr); // 记得用完 munmap(addr, sb.st_size); close(fd);
-
PROT_READ是底线,加PROT_WRITE不代表能写成功(取决于MAP_PRIVATE/MAP_SHARED) -
MAP_PRIVATE修改不落盘,适合只读场景;MAP_SHARED写后可被其他进程看到,但需msync()保证落盘 - 不要对
mmap()返回指针做delete或free——必须用munmap()
读取时踩得最多的三个指针越界坑
映射后拿到的是 raw memory 地址,编译器不会帮你检查数组边界。一旦越界,轻则读到脏数据,重则触发 SEGV_MAPERR(Linux)或直接 crash(Windows 上 MapViewOfFile 类似)。
- 误用
sizeof(data):data是指针,sizeof(data)永远是 8(64 位),不是文件大小——必须自己维护size_in_bytes - 整型类型不匹配:文件存的是
int16_t,却用int32_t*去读,每步偏移错 2 字节,数据全乱 - 未考虑字节序:跨平台二进制文件(如网络包 dump)必须用
ntohl()/htons()转换,不能直接解引用
推荐做法:封装一个带长度检查的访问器,例如 get_uint32_at(size_t offset),内部先断言 offset + sizeof(uint32_t) 。
立即学习“C++免费学习笔记(深入)”;
Windows 下等价实现要注意什么?
Windows 没有 mmap(),对应的是 CreateFileMapping() + MapViewOfFile()。关键差异在于:映射句柄(HANDLE)和视图地址(LPVOID)是分离的,且 MapViewOfFile() 的 dwNumberOfBytesToMap 参数不能为 0(Linux 下 mmap() 的 length=0 表示映射整个文件,Windows 必须显式传大小)。
- 必须先用
GetFileSizeEx()获取真实大小,不能依赖GetFileSize()(高位丢失) -
CreateFileMapping()的flProtect用PAGE_READONLY,对应 Linux 的PROT_READ - 映射失败时返回
nullptr,不是INVALID_HANDLE_VALUE——别混淆句柄和地址
跨平台代码建议:用宏或 RAII 封装,把 open/mmap/munmap/close 和 CreateFile/CreateFileMapping/MapViewOfFile/UnmapViewOfFile/CloseHandle 分两套实现,避免混用逻辑。

