如何处理 C++ 中的 bad_alloc?

有一个名为 foo 的方法有时会返回以下错误:

一般情况下,您不能不应该尝试对该错误作出响应。bad_alloc表示由于可用内存不足而无法分配资源。在大多数情况下,你的程序无法应对这种情况,而尽快终止是唯一有意义的行为。

更糟糕的是,现代操作系统经常过度分配资源:在这种系统上,即使剩余的可用内存不足,mallocnew也能产生有效的指针--std::bad_alloc永远不会被抛出,或者至少不是内存耗尽的可靠信号。相反,如果尝试访问已分配的内存,就会导致分段故障,而这种故障是无法捕获的(你可以处理分段故障信号,但之后就无法恢复程序了)。

当捕获到std::bad_alloc时,你唯一能做的也许就是记录错误,并尝试通过释放未释放的资源来确保程序安全终止(但如果程序适当使用 RAII,在抛出错误后的正常堆栈解卷过程中,堆栈解卷会自动完成)。

在某些情况下,程序可能会尝试释放一些内存并重试,或使用二级内存(= 磁盘)代替 RAM,但这些机会只存在于条件严格的特定场景中:

1.应用程序必须确保运行在不会超量分配内存的系统上,即在分配时就发出失败信号,而不是稍后再分配。 2.应用程序必须能够立即释放内存,在此期间不得再意外分配内存。

应用程序能控制第 1 点的情况极为罕见-- 用户空间应用程序从未**控制过,这是一个系统范围的设置,需要 root 权限才能更改;

好了,假设你已经解决了第 1 点。例如,您现在可以对部分数据使用 LRU 缓存(可能是一些可以按需再生或重新加载的特别大的业务对象)。接下来,您需要将可能失败的实际逻辑放到一个支持重试的函数中--换句话说,如果它被中止,您可以重新启动它:

lru_cache widget_cache;

double perform_operation(int widget_id) {
    std::optional maybe_widget = widget_cache.find_by_id(widget_id);
    if (not maybe_widget) {
        maybe_widget = widget_cache.store(widget_id, load_widget_from_disk(widget_id));
    }
    return maybe_widget->frobnicate();
}

...

for (int num_attempts = 0; num_attempts < MAX_NUM_ATTEMPTS; ++num_attempts) {
    try {
        return perform_operation(widget_id);
    } catch (std::bad_alloc const&) {
        if (widget_cache.empty()) throw; // 内存错误。
        如果是,widget_cache.remove_oldest();
    }
}

// 在此处处理过多的失败尝试。

但即使在这里,使用 std::set_new_handler 代替处理 std::bad_alloc 也能带来同样的好处,而且会简单得多。


1 如果你正在创建一个**控制点 1 的应用程序,并且你正在阅读本答案,请给我发一封电子邮件,我对你的情况真的很好奇。

评论(26)
解决办法

你可以像捕捉其他异常一样捕捉它:

try {
  foo();
}
catch (const std::bad_alloc&) {
  return -1;
}

至于从这一点出发能做些什么,取决于你自己,但在技术上绝对是可行的。

评论(0)

我不建议这样做,因为 "bad_alloc "意味着内存*用完。最好的办法是放弃,而不是尝试恢复。不过,这就是你所要求的解决方案:

try {
    foo();
} catch ( const std::bad_alloc& e ) {
    return -1;
}
评论(5)