标准C++的operator new分配失败时默认抛std::bad_alloc,但应改用new(std::nothrow)返回nullptr以避免异常嵌套;全局重载operator new易引发未定义行为,应仅重载类专属版本。

内存分配失败时,operator new 抛异常还是返回 nullptr?
标准 C++ 的 operator new 在分配失败时默认抛 std::bad_alloc,但这个行为在资源耗尽场景下反而危险——你可能正处在异常处理路径中,再抛异常会直接调用 std::terminate。更稳妥的做法是使用无抛出版本:operator new(std::nothrow),它失败时返回 nullptr,让你能主动判断并降级。
注意:全局重载 operator new 并统一返回 nullptr 是常见误操作。它会影响所有 new 表达式(包括 STL 容器内部),而很多标准组件(如 std::vector::push_back)并未为 nullptr 返回做防御,容易引发未定义行为。真正可控的方式是只在关键路径显式使用 new(std::nothrow),或封装自己的分配器。
- 不要重载全局
operator new来“统一处理 OOM” - 对核心对象(如连接池节点、帧缓冲区)优先用
new(std::nothrow) - 若必须重载,仅重载类专属的
operator new,且确保析构函数不依赖该对象生命周期外的资源
句柄耗尽前,如何预判并触发文件/网络句柄的主动释放?
Windows 和 Linux 对进程句柄数有硬限制(Linux 通常 ulimit -n 控制),但等 open() 或 socket() 真的返回 -1 才响应,往往已错过降级窗口。正确做法是建立句柄水位监控,在达到阈值(如 80%)时就触发清理策略。
例如:维护一个 std::unordered_map<int fileguard></int> 记录所有活跃句柄,并在每次打开前检查当前数量。一旦超限,按 LRU 或业务优先级(如临时缓存文件 > 持久配置文件)调用 close();同时避免在信号处理函数中做此操作——close() 不是异步信号安全函数。
立即学习“C++免费学习笔记(深入)”;
- 不要等到
errno == EMFILE或ENFILE才行动 - 监控应包含内核级句柄计数(
/proc/self/status中FDSize和FDs字段,或 Windows 的NtQueryInformationProcess) - 释放动作必须在主线程或专用管理线程执行,不可在
SIGUSR1等信号上下文中调用close()
降级策略本身不能成为新故障源:为什么 std::ofstream 不适合写降级日志?
当系统已接近资源枯竭时,std::ofstream 构造过程可能隐式调用 new 分配缓冲区、尝试 locale 初始化、甚至触发静态对象构造——这些都可能再次失败。真正的降级日志必须是“零分配、零异常、最小系统调用”的。
推荐方案是预分配固定大小环形缓冲区(如 64KB),配合 write(2) 直接写入已打开的 fd(如 STDERR_FILENO 或预先 open("/dev/log", O_WRONLY | O_NONBLOCK) 的句柄)。这样绕过 C 运行时和 C++ 流缓冲层,确保最后一行日志能落盘。
- 避免在降级路径中使用任何 STL 容器、流、字符串构造
- 预开一个
O_CLOEXEC | O_NONBLOCK的 fd,专用于紧急日志,且永不 close - 降级逻辑里禁止调用
malloc、printf、std::string::c_str()等潜在分配点
RAII 在资源耗尽时是否还可靠?
RAII 本身不解决资源耗尽问题,它只保证“已有资源”的释放顺序。但如果析构函数里尝试释放资源时又触发新分配(比如日志记录、网络上报、清理线程唤醒),就会陷入死循环或崩溃。因此,所有关键 RAII 类的析构函数必须是 noexcept,且内部禁用任何可能失败的操作。
例如,一个网络连接类的析构函数不应尝试发送 FIN 包后等待 ACK(可能阻塞或触发 socket 分配);而应只调用 close(fd),把连接状态清理交给内核。同理,文件句柄类析构中不调用 fclose()(它会 flush 缓冲区,可能分配内存),改用 close(fd)。
- 所有 RAII 析构函数必须声明为
noexcept - 析构中只做确定性系统调用(
close、munmap、pthread_mutex_destroy),禁用任何封装层 - 资源释放失败(如
close返回 -1)只能记录到预分配缓冲区,不可抛异常或重试


















