最直接方式是用 setrlimit 设置 RLIMIT_AS(地址空间限制),需在 main 开头调用,单位字节,覆盖 mmap、STL 匿名映射等;Windows 无等价机制,须靠外部 Job Object;同时必须检查 malloc/new 失败并处理异常,配合预分配与自定义分配器减少碎片。

Linux 下用 setrlimit 限制进程虚拟内存(RLIMIT_AS)最直接
在 Linux 环境下,setrlimit 是 C++ 程序自身控制内存上限最轻量、最可靠的方式。它作用于当前进程及其所有子线程,内核会在分配失败时直接返回 ENOMEM,而不是让程序慢慢 OOM 被杀。
常见错误是只设 RLIMIT_DATA(堆数据段),但它不涵盖 mmap、STL 容器内部的匿名映射、或 std::string 的小字符串优化外存——这些全算进 RLIMIT_AS(address space)。所以必须用 RLIMIT_AS。
- 调用位置:务必在
main()开头、任何大内存分配(如全局容器初始化、大型静态对象构造)之前执行 - 单位是字节:
struct rlimit rl = {1024UL * 1024 * 1024, 1024UL * 1024 * 1024}; // 1GB - 检查返回值:
if (setrlimit(RLIMIT_AS, &rl) != 0) { perror("setrlimit"); },权限不足(如被 systemd 或容器限制)会导致失败 - 注意:该限制对
fork()后的子进程无效,需在子进程中重新设置
Windows 上没有等价 setrlimit,得靠 SetProcessWorkingSetSizeEx + Job Object
Windows 没有用户态可设的硬性虚拟内存上限机制。SetProcessWorkingSetSizeEx 只能建议系统回收物理内存页,不能阻止 VirtualAlloc 成功——它只是“软限”,程序仍可能因提交内存超物理+页文件而崩溃。
真正起作用的是 Job Object,但必须在进程创建前由父进程设置(C++ 程序自身无法给自己加 Job Object)。所以如果你是主程序,只能靠外部启动器(如 PowerShell 脚本)包装:
立即学习“C++免费学习笔记(深入)”;
$job = New-JobObject $job | Set-JobObjectLimit -ProcessMemoryLimit 1GB Start-Process -FilePath ".\myapp.exe" -CreationFlags 0x00000008 -JobObject $job
否则,在程序内调用 CreateJobObject 再 AssignProcessToJobObject 会失败,报错 ERROR_ACCESS_DENIED。
malloc 失败后不检查,new 不配 nothrow,等于白设内存上限
即使 setrlimit 生效,C++ 默认行为仍是:当 operator new 分配失败,抛出 std::bad_alloc;malloc 返回 nullptr。但大量代码忽略这点,直接解引用,结果触发 SIGSEGV,掩盖了真实的内存耗尽问题。
- 用
new(std::nothrow)替代裸new,显式检查指针是否为nullptr - STL 容器(如
std::vector::reserve)内部调用new,失败时抛异常,必须用try/catch(std::bad_alloc&)包裹 -
std::string构造或拼接、std::map插入大量元素,都可能隐式分配——别假设“小数据就安全” - 第三方库(如 Boost、OpenCV)若未声明异常规格,也可能抛
bad_alloc,需查文档或实测
容器预分配和自定义分配器不是“绕过限制”的技巧,而是避免触发限制的关键手段
频繁的小块 new 会产生大量内存碎片,导致 RLIMIT_AS 还没到,mmap 就因找不到连续地址空间而失败(尤其 32 位或长期运行进程)。这不是 bug,是地址空间管理的固有现象。
解决思路不是放宽限制,而是减少分配次数和碎片:
- 用
std::vector::reserve(n)预留足够空间,避免多次扩容重拷贝 - 对已知生命周期的对象池,用
std::pmr::monotonic_buffer_resource(C++17)做栈式分配,释放零开销 - 慎用
std::shared_ptr:每个对象额外带控制块,且分散分配;改用std::unique_ptr+ 对象池更可控 - 禁用
std::string的 SSO(small string optimization)无意义;重点是避免反复构造/析构临时字符串
真正的难点不在设限,而在让所有内存路径都对 ENOMEM 敏感——从 new 到 mmap,从 STL 到第三方库,每一层都要暴露失败信号。漏掉任意一环,限制就形同虚设。










