64

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

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Abort

有没有办法可以使用try-catch块来阻止此错误终止我的程序(我想要做的就是 return -1)?

如果是这样,它的语法是什么?

bad_alloc在 C++中我还能如何处理?

4

7 回答 7

92

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

更糟糕的是,现代操作系统经常过度分配:在这样的系统上,即使没有足够的空闲内存也可以返回一个有效的指针——malloc永远不会被抛出,或者至少不是内存耗尽的可靠迹象。相反,尝试访问分配的内存将导致无法捕获的分段错误(您可以处理分段错误信号,但之后无法恢复程序)。newstd::bad_alloc

捕获时您唯一可以做std::bad_alloc的就是记录错误,并尝试通过释放未完成的资源来确保安全的程序终止(但如果程序使用 RAII,则在抛出错误后的正常堆栈展开过程中会自动完成此操作适当)。

在某些情况下,程序可能会尝试释放一些内存并重试,或者使用辅助内存(= 磁盘)而不是 RAM,但这些机会仅存在于具有严格条件的非常特定的场景中:

  1. 应用程序必须确保它在不会过度使用内存的系统上运行,即它在分配时而不是稍后发出失败信号。
  2. 应用程序必须能够立即释放内存,同时没有任何进一步的意外分配。

应用程序控制第 1 点的情况极为罕见——用户空间应用程序永远不会这样做,这是一个系统范围的设置,需要 root 权限才能更改。1

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

lru_cache<widget> widget_cache;

double perform_operation(int widget_id) {
    std::optional<widget> 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; // memory error elsewhere.
        widget_cache.remove_oldest();
    }
}

// Handle too many failed attempts here.

但即使在这里,使用std::set_new_handler而不是处理std::bad_alloc提供了相同的好处并且会更简单。


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

于 2012-02-26T20:15:59.920 回答
40

c++中 C++ 标准指定的行为是new什么?

通常的概念是,如果new操作员无法分配所请求大小的动态内存,那么它应该抛出 type 的异常std::bad_alloc。但是,甚至在抛出异常
之前还会发生更多事情:bad_alloc

C++03 第 3.7.4.1.3 节:

分配存储失败的分配函数可以调用当前安装的 new_handler(18.4.2.2),如果有的话。[注意:程序提供的分配函数可以使用 set_new_handler 函数 (18.4.2.3) 获取当前安装的 new_handler 的地址。] 如果使用空异常规范 (15.4) 声明的分配函数 throw() 失败分配存储,它应该返回一个空指针。任何其他分配存储失败的分配函数只能通过抛出类 std::bad_alloc (18.4.2.1) 或从 std::bad_alloc 派生的类的异常来指示失败。

考虑以下代码示例:

#include <iostream>
#include <cstdlib>

// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
    std::cerr << "Unable to satisfy request for memory\n";

    std::abort();
}

int main()
{
    //set the new_handler
    std::set_new_handler(outOfMemHandler);

    //Request huge memory size, that will cause ::operator new to fail
    int *pBigDataArray = new int[100000000L];

    return 0;
}

在上面的例子中,operator new(很可能)将无法为 100,000,000 个整数分配空间,函数outOfMemHandler()将被调用,程序将在发出错误消息后中止。

new如此处所示,操作员在无法满足内存请求时的默认行为是new-handler重复调用该函数,直到它可以找到足够的内存或没有更多新的处理程序。在上面的例子中,除非我们调用,否则std::abort()outOfMemHandler()重复调用。因此,处理程序要么确保下一次分配成功,要么注册另一个处理程序,或者不注册处理程序,或者不返回(即终止程序)。如果没有新的处理程序并且分配失败,则操作员将抛出异常。

new_handler和是什么set_new_handler

new_handler是指向函数的指针的 typedef,该函数不接受和返回任何内容,并且set_new_handler是一个接受并返回 a 的函数new_handler

就像是:

typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();

set_new_handler 的参数是一个指针,指向函数操作符new如果不能分配所请求的内存就应该调用。它的返回值是指向先前注册的处理函数的指针,如果没有先前的处理函数,则返回 null。

如何在 C++ 中处理内存不足的情况?

鉴于new设计良好的用户程序的行为应该通过提供new_handler执行以下操作之一的正确来处理内存不足的情况:

提供更多可用内存:这可能允许 operator new 循环内的下一次内存分配尝试成功。实现这一点的一种方法是在程序启动时分配一大块内存,然后在第一次调用 new-handler 时释放它以供程序使用。

安装不同的新处理程序:如果当前新处理程序无法提供更多可用内存,并且有另一个新处理程序可以,则当前新处理程序可以在其位置安装另一个新处理程序(通过调用set_new_handler)。下一次 operator new 调用 new-handler 函数时,它将获得最近安装的那个。

(这个主题的一个变体是让 new-handler 修改自己的行为,所以下次调用它时,它会做一些不同的事情。实现这一点的一种方法是让 new-handler 修改静态的、特定于命名空间的或影响新处理程序行为的全局数据。)

卸载新处理程序: 这是通过将空指针传递给set_new_handler. 如果没有安装 new-handler,当内存分配不成功时operator new会抛出异常 ((convertible to) )。std::bad_alloc

抛出可转换为std::bad_alloc. 此类异常不会被 捕获operator new,但会传播到发起内存请求的站点。

不返回:通过调用abortor exit

于 2012-02-27T03:33:02.473 回答
39

您可以像任何其他异常一样捕获它:

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

从这一点开始,你能做的有用的事情完全取决于你,但在技术上它绝对是可行的。

于 2012-02-26T20:15:35.983 回答
9

我不建议这样做,因为这bad_alloc意味着您内存不足。最好是放弃而不是试图恢复。但是,这是您要求的解决方案:

try {
    foo();
} catch ( const std::bad_alloc& e ) {
    return -1;
}
于 2012-02-26T20:15:35.733 回答
5

我可能会为此建议一个更简单(甚至更快)的解决方案。new如果无法分配内存,运算符将返回 null。

int fv() {
    T* p = new (std::nothrow) T[1000000];
    if (!p) return -1;
    do_something(p);
    delete p;
    return 0;
}

我希望这会有所帮助!

于 2014-06-12T13:06:24.153 回答
1

让您的foo 程序以受控方式退出:

#include <stdlib.h>     /* exit, EXIT_FAILURE */

try {
    foo();
} catch (const std::bad_alloc&) {
    exit(EXIT_FAILURE);
}

然后编写一个调用实际程序的shell程序。由于地址空间是分开的,所以你的 shell 程序的状态总是很明确的。

于 2014-03-12T09:35:20.657 回答
1

当然你可以抓住 a bad_alloc,但我认为更好的问题是你如何能从一开始就阻止 abad_alloc的发生。

通常,bad_alloc这意味着内存分配出现问题 - 例如,当您内存不足时。如果您的程序是 32 位的,那么当您尝试分配 >4 GB 时就会发生这种情况。当我将 C 字符串复制到 QString 时,这发生在我身上一次。C 字符串不是以 '\0' 结尾的,这导致strlen函数返回数十亿的值。然后它尝试分配几 GB 的 RAM,这导致bad_alloc.

我还看到bad_alloc我不小心访问了构造函数的初始化列表中的未初始化变量。我foo和一个成员一起上课T bar。在构造函数中,我想用参数中的值初始化成员:

foo::foo(T baz) // <-- mistyped: baz instead of bar
: bar(bar)
{
}

因为我输入了错误的参数,所以构造函数用自身初始化了 bar(所以它读取了一个未初始化的值!)而不是参数。

valgrind 对此类错误非常有帮助!

于 2020-08-11T14:37:48.347 回答