如何将Linux下mmap映射的内存文件封装为std::vector?
回答
首先明确:不推荐使用std::vector封装mmap映射内存
原因如下:
std::vector的核心设计语义是独占内存所有权,会自主完成内存分配、扩容、释放逻辑,强行注入外部预分配的mmap内存完全违背其设计初衷。即便通过特殊的自定义分配器实现,后续调用push_back、resize等需要扩容的接口时,也极易触发未定义行为,维护成本极高。- C标准要求同类型分配器必须语义等价、可互相拷贝并释放对方分配的内存,你需要将文件描述符、映射地址等状态存入分配器的方案,会直接打破这个要求,哪怕C11之后支持有状态分配器,实际使用中兼容坑非常多,完全没必要投入成本。
推荐的标准库方案:使用std::span(C++20及以上)
std::span是标准库专门设计的非拥有式连续内存视图,完美匹配你的需求:
- 它提供了和
std::vector几乎一致的访问接口:包括operator[]、at()、迭代器、size()、data()等,完全可以替代vector满足你避免使用原始指针解析二进制数据的需求,也完全兼容所有标准库算法。 - 构造逻辑极其简单,不需要修改分配器,拿到mmap返回的地址和映射长度后直接构造即可:
// 假设mmap返回地址为mmap_addr,映射长度为file_size std::span<const std::byte> mapped_buf(reinterpret_cast<const std::byte*>(mmap_addr), file_size);
如果你使用的C++版本低于20,也可以使用兼容实现的gsl::span,或者自行实现极简的视图类,成本远低于自定义vector分配器。
如果需要自动管理映射和文件的生命周期,可自行实现RAII封装类
如果你希望同时自动处理munmap和close逻辑,只需要写一个简单的RAII类,对外暴露std::span接口即可,示例实现如下:
#include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <span> #include <stdexcept> class MappedFile { public: MappedFile(const char* path, size_t length, int prot = PROT_READ, int flags = MAP_PRIVATE) : length_(length) { fd_ = open(path, O_RDONLY); if (fd_ < 0) throw std::runtime_error("Failed to open file"); addr_ = mmap(nullptr, length_, prot, flags, fd_, 0); if (addr_ == MAP_FAILED) { close(fd_); throw std::runtime_error("Failed to create mmap"); } } ~MappedFile() { if (addr_ != MAP_FAILED) munmap(addr_, length_); if (fd_ >= 0) close(fd_); } // 禁止拷贝避免重复释放 MappedFile(const MappedFile&) = delete; MappedFile& operator=(const MappedFile&) = delete; // 支持移动语义 MappedFile(MappedFile&& other) noexcept : fd_(other.fd_), addr_(other.addr_), length_(other.length_) { other.fd_ = -1; other.addr_ = MAP_FAILED; other.length_ = 0; } MappedFile& operator=(MappedFile&& other) noexcept { if (this != &other) { this->~MappedFile(); fd_ = other.fd_; addr_ = other.addr_; length_ = other.length_; other.fd_ = -1; other.addr_ = MAP_FAILED; other.length_ = 0; } return *this; } // 暴露span接口,可直接当做连续容器使用 std::span<const std::byte> span() const noexcept { return {reinterpret_cast<const std::byte*>(addr_), length_}; } std::span<std::byte> span() noexcept { return {reinterpret_cast<std::byte*>(addr_), length_}; } // 兼容vector的常用接口 size_t size() const noexcept { return length_; } const std::byte& operator[](size_t idx) const noexcept { return span()[idx]; } std::byte& operator[](size_t idx) noexcept { return span()[idx]; } auto begin() const noexcept { return span().begin(); } auto end() const noexcept { return span().end(); } auto begin() noexcept { return span().begin(); } auto end() noexcept { return span().end(); } private: int fd_ = -1; void* addr_ = MAP_FAILED; size_t length_ = 0; };
使用时直接创建MappedFile实例即可,完全不需要手动管理资源,也可以像使用vector一样访问内存、配合标准库算法使用。
内容的提问来源于stack exchange,提问作者Cognitive Hazard
相关产品推荐
相关产品推荐

