mremap仅支持mmap(map_anonymous)分配的页对齐内存,因new/malloc内存不满足vma独立性与页对齐要求,调用会失败并设errno=einval。

为什么 mremap 不能直接用于普通 new 或 malloc 分配的内存
因为 mremap 只作用于通过 mmap(且带 M MAP_ANONYMOUS)分配的页对齐内存块。用 new、malloc 或 std::vector 分配的内存由堆管理器维护,其地址不保证页对齐,也不在内核的 VMA(Virtual Memory Area)中独立成区——mremap 会直接返回 MAP_FAILED 并设 errno = EINVAL。
实操建议:
- 必须用
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)分配起始内存 - 分配大小必须是系统页大小(通常为
4096)的整数倍,可用getpagesize()获取 - 后续所有扩容/缩容都只能基于该
mmap返回的指针,不能混用free或delete
mremap 扩容失败时最常遇到的三个错误
不是所有扩容都能成功。内核需要在原映射后方“拼接”新页,这依赖虚拟地址空间连续性与权限一致性。
常见错误现象与应对:
立即学习“C++免费学习笔记(深入)”;
-
errno == ENOMEM:目标地址空间被占用(如栈、其他mmap区域插在中间),此时mremap不会自动找空洞,必须手动mmap新区域 +memcpy+munmap原区——已非“无拷贝” -
errno == EFAULT:传入的旧地址不是有效mmap起始地址(比如偏移了几个字节),检查是否直接用了数组下标指针而非原始mmap返回值 -
errno == EAGAIN:系统临时无法满足大块连续 VMA(尤其在低内存或碎片化严重时),应退回到传统拷贝扩容路径,不能重试
如何安全地把 mremap 接入类模板(比如仿 std::vector)
关键不在扩容逻辑本身,而在生命周期管理和异常安全:C++ 对象构造/析构不能跨 mremap 边界隐式发生。
实操要点:
- 只用
mremap管理原始内存(char*或std::byte*),对象必须用placement new显式构造,销毁时显式调用析构函数 - 扩容前,需先在新地址范围用
placement new构造新增元素;缩容时,必须先显式析构被截断的尾部对象,再调mremap - 禁止对含非平凡析构函数的类型(如
std::string)做MREMAP_MAYMOVE,因移动后旧地址析构会 crash——除非你确保旧地址内容已全部析构完毕
性能和可移植性的真实代价
mremap 的“无拷贝”仅指用户态数据不 memcpy,但内核仍要更新页表项、可能触发 TLB shootdown,在多核高竞争场景下反而比小规模 memcpy 慢。
更现实的限制:
- Linux 特有,macOS / Windows 完全不支持,无法跨平台抽象
- glibc 封装的
realloc绝不会用mremap处理malloc内存,别指望标准容器自动优化 - 调试困难:GDB 和 AddressSanitizer 默认不识别
mmap内存的边界,越界访问可能静默失败或报错位置漂移
真正需要它的地方很少:通常是自研高性能 ring buffer、共享内存池或实时音视频帧缓存——而且得接受它和 RAII、STL 容器天然互斥。









