简体   繁体   English

如何在 C++ 中处理 bad_alloc?

[英]How to deal with bad_alloc in C++?

There is a method called foo that sometimes returns the following error:有一个名为foo的方法有时会返回以下错误:

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

Is there a way that I can use a try - catch block to stop this error from terminating my program (all I want to do is return -1 )?有没有办法可以使用try - catch块来阻止此错误终止我的程序(我只想返回-1 )?

If so, what is the syntax for it?如果是这样,它的语法是什么?

How else can I deal with bad_alloc in C++?我还能如何处理 C++ 中的bad_alloc

In general you cannot , and should not try , to respond to this error.通常,您不能也不应该尝试响应此错误。 bad_alloc indicates that a resource cannot be allocated because not enough memory is available. bad_alloc表示由于没有足够的可用内存而无法分配资源。 In most scenarios your program cannot hope to cope with that, and terminating soon is the only meaningful behaviour.在大多数情况下,您的程序无法应对这种情况,并且尽快终止是唯一有意义的行为。

Worse, modern operating systems often over-allocate: on such systems, malloc and new can return a valid pointer even if there is not enough free memory left – std::bad_alloc will never be thrown, or is at least not a reliable sign of memory exhaustion.更糟糕的是,现代操作系统经常过度分配:在这样的系统上,即使没有足够的可用内存, mallocnew也可以返回一个有效的指针std::bad_alloc永远不会被抛出,或者至少不是一个可靠的标志内存耗尽。 Instead, attempts to access the allocated memory will then result in a segmentation fault, which is not catchable (you can handle the segmentation fault signal, but you cannot resume the program afterwards).相反,尝试访问分配的内存将导致无法捕获的段错误(您可以处理段错误信号,但之后无法恢复程序)。

The only thing you could do when catching std::bad_alloc is to perhaps log the error, and try to ensure a safe program termination by freeing outstanding resources (but this is done automatically in the normal course of stack unwinding after the error gets thrown if the program uses RAII appropriately).捕获std::bad_alloc时你唯一能做的就是记录错误,并尝试通过释放未完成的资源来确保安全的程序终止(但这是在错误被抛出后的正常堆栈展开过程中自动完成的,如果该程序适当地使用了 RAII)。

In certain cases, the program may attempt to free some memory and try again, or use secondary memory (= disk) instead of RAM but these opportunities only exist in very specific scenarios with strict conditions:在某些情况下,程序可能会尝试释放一些内存并重试,或者使用辅助内存(= 磁盘)而不是 RAM,但这些机会仅存在于具有严格条件的非常特定的场景中:

  1. The application must ensure that it runs on a system that does not overcommit memory , ie it signals failure upon allocation rather than later.应用程序必须确保它运行在一个不会过度使用内存的系统上,即它在分配时而不是稍后发出失败信号。
  2. The application must be able to free memory immediately , without any further accidental allocations in the meantime.应用程序必须能够立即释放内存,在此期间没有任何进一步的意外分配。

It's exceedingly rare that applications have control over point 1 — userspace applications never do, it's a system-wide setting that requires root permissions to change.应用程序控制第 1 点的情况极为罕见——用户空间应用程序永远不会这样做,这是一个系统范围的设置,需要 root 权限才能更改。 1 1

OK, so let's assume you've fixed point 1. What you can now do is for instance use a LRU cache for some of your data (probably some particularly large business objects that can be regenerated or reloaded on demand).好的,让我们假设您已经确定了第 1 点。例如,您现在可以做的是对某些数据使用LRU 缓存(可能是一些可以按需重新生成或重新加载的特别大的业务对象)。 Next, you need to put the actual logic that may fail into a function that supports retry — in other words, if it gets aborted, you can just relaunch it:接下来,您需要将可能失败的实际逻辑放入支持重试的函数中——换句话说,如果它被中止,您可以重新启动它:

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.

But even here, using std::set_new_handler instead of handling std::bad_alloc provides the same benefit and would be much simpler.但即使在这里,使用std::set_new_handler而不是处理std::bad_alloc提供相同的好处,而且会简单得多。


1 If you're creating an application that does control point 1, and you're reading this answer, please shoot me an email, I'm genuinely curious about your circumstances. 1如果你创建控制点1的应用程序,你读这个答案,请拍我的电子邮件,我真的很好奇你的情况。

What is the C++ Standard specified behavior of new in c++? C++ 中new的 C++ 标准规定的行为是什么?

The usual notion is that if new operator cannot allocate dynamic memory of the requested size, then it should throw an exception of type std::bad_alloc .通常的概念是,如果new运算符不能分配请求大小的动态内存,那么它应该抛出类型为std::bad_alloc的异常。
However, something more happens even before a bad_alloc exception is thrown:然而,甚至在抛出bad_alloc异常之前bad_alloc发生更多的事情:

C++03 Section 3.7.4.1.3: says C++03 第 3.7.4.1.3 节:

An allocation function that fails to allocate storage can invoke the currently installed new_handler(18.4.2.2), if any.分配存储失败的分配函数可以调用当前安装的 new_handler(18.4.2.2),如果有的话。 [Note: A program-supplied allocation function can obtain the address of the currently installed new_handler using the set_new_handler function (18.4.2.3).] If an allocation function declared with an empty exception-specification (15.4), throw(), fails to allocate storage, it shall return a null pointer. [注意:程序提供的分配函数可以使用 set_new_handler 函数 (18.4.2.3) 获取当前安装的 new_handler 的地址。] 如果使用空异常规范 (15.4) 声明的分配函数 throw() 失败分配存储,它应返回一个空指针。 Any other allocation function that fails to allocate storage shall only indicate failure by throw-ing an exception of class std::bad_alloc (18.4.2.1) or a class derived from std::bad_alloc.未能分配存储的任何其他分配函数应仅通过抛出类 std::bad_alloc (18.4.2.1) 或从 std::bad_alloc 派生的类的异常来指示失败。

Consider the following code sample:考虑以下代码示例:

#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;
}

In the above example, operator new (most likely) will be unable to allocate space for 100,000,000 integers, and the function outOfMemHandler() will be called, and the program will abort after issuing an error message.在上面的例子中, operator new (很可能)将无法为 100,000,000 个整数分配空间,并且函数outOfMemHandler()将被调用,并且程序将在发出错误消息后中止

As seen here the default behavior of new operator when unable to fulfill a memory request, is to call the new-handler function repeatedly until it can find enough memory or there is no more new handlers.如这里所见,当无法满足内存请求时, new运算符的默认行为是重复调用new-handler函数,直到它可以找到足够的内存或没有更多的新处理程序。 In the above example, unless we call std::abort() , outOfMemHandler() would be called repeatedly .在上面的例子中,除非我们调用std::abort()outOfMemHandler()将被重复调用 Therefore, the handler should either ensure that the next allocation succeeds, or register another handler, or register no handler, or not return (ie terminate the program).因此,处理程序要么确保下一次分配成功,要么注册另一个处理程序,要么不注册处理程序,要么不返回(即终止程序)。 If there is no new handler and the allocation fails, the operator will throw an exception.如果没有新的处理程序并且分配失败,则操作员将抛出异常。

What is the new_handler and set_new_handler ?什么是new_handlerset_new_handler

new_handler is a typedef for a pointer to a function that takes and returns nothing, and set_new_handler is a function that takes and returns a new_handler . new_handler是一个指向一个函数的指针的 typedef,它不接受和返回什么,而set_new_handler是一个接受并返回一个new_handler

Something like:就像是:

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

set_new_handler's parameter is a pointer to the function operator new should call if it can't allocate the requested memory. set_new_handler 的参数是一个指向函数 operator new的指针,如果它不能分配请求的内存,它应该调用。 Its return value is a pointer to the previously registered handler function, or null if there was no previous handler.它的返回值是指向先前注册的处理程序函数的指针,如果没有先前的处理程序,则返回 null。

How to handle out of memory conditions in C++?如何处理 C++ 中的内存不足情况?

Given the behavior of new a well designed user program should handle out of memory conditions by providing a proper new_handler which does one of the following:鉴于new的行为,设计良好的用户程序应该通过提供适当的new_handler来处理内存不足的情况,它执行以下操作之一:

Make more memory available: This may allow the next memory allocation attempt inside operator new's loop to succeed.提供更多可用内存:这可能允许 operator new 循环内的下一次内存分配尝试成功。 One way to implement this is to allocate a large block of memory at program start-up, then release it for use in the program the first time the new-handler is invoked.实现这一点的一种方法是在程序启动时分配一大块内存,然后在第一次调用 new-handler 时将其释放以供程序使用。

Install a different new-handler: If the current new-handler can't make any more memory available, and of there is another new-handler that can, then the current new-handler can install the other new-handler in its place (by calling set_new_handler ).安装不同的 new-handler:如果当前的 new-handler 不能提供更多内存可用,并且有另一个 new-handler 可以,那么当前的 new-handler 可以在它的位置安装另一个 new-handler (通过调用set_new_handler )。 The next time operator new calls the new-handler function, it will get the one most recently installed.下一次 operator new 调用 new-handler 函数时,它将获取最近安装的函数。

(A variation on this theme is for a new-handler to modify its own behavior, so the next time it's invoked, it does something different. One way to achieve this is to have the new-handler modify static, namespace-specific, or global data that affects the new-handler's behavior.) (这个主题的一个变体是 new-handler 修改自己的行为,所以下次调用它时,它会做一些不同的事情。实现这一点的一种方法是让 new-handler 修改静态的、特定于命名空间的或影响新处理程序行为的全局数据。)

Uninstall the new-handler: This is done by passing a null pointer to set_new_handler .卸载新处理程序:这是通过将空指针传递给set_new_handlerset_new_handler With no new-handler installed, operator new will throw an exception ((convertible to) std::bad_alloc ) when memory allocation is unsuccessful.如果没有安装新的处理程序,当内存分配不成功时, operator new将抛出异常((可转换为) std::bad_alloc )。

Throw an exception convertible to std::bad_alloc .抛出可转换为std::bad_alloc的异常 Such exceptions are not be caught by operator new , but will propagate to the site originating the request for memory.此类异常不会被operator new捕获,但会传播到发起内存请求的站点。

Not return: By calling abort or exit .不返回:通过调用abortexit

You can catch it like any other exception:您可以像其他任何异常一样捕获它:

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

Quite what you can usefully do from this point is up to you, but it's definitely feasible technically.从这一点来看,您可以做什么有用取决于您,但这在技术上绝对是可行的。

I would not suggest this, since bad_alloc means you are out of memory .我不建议这样做,因为bad_alloc意味着你的内存不足 It would be best to just give up instead of attempting to recover.最好是放弃而不是试图恢复。 However here is is the solution you are asking for:但是,这是您要求的解决方案:

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

I may suggest a more simple (and even faster) solution for this.我可能会为此建议一个更简单(甚至更快)的解决方案。 new operator would return null if memory could not be allocated.如果无法分配内存, new运算符将返回 null。

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

I hope this could help!我希望这会有所帮助!

Let your foo program exit in a controlled way:让您的foo 程序以受控方式退出

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

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

Then write a shell program that calls the actual program.然后编写一个调用实际程序的shell程序。 Since the address spaces are separated, the state of your shell program is always well-defined.因为地址空间是分开的,所以你的 shell 程序的状态总是明确定义的。

Of course you can catch a bad_alloc , but I think the better question is how you can stop a bad_alloc from happening in the first place.当然,您可以捕获bad_alloc ,但我认为更好的问题是首先如何阻止bad_alloc的发生。

Generally, bad_alloc means that something went wrong in an allocation of memory - for example when you are out of memory.通常, bad_alloc意味着内存分配出现问题 - 例如,当您内存不足时。 If your program is 32-bit, then this already happens when you try to allocate >4 GB.如果您的程序是 32 位的,那么当您尝试分配 >4 GB 时,这已经发生了。 This happened to me once when I copied a C-string to a QString.当我将 C 字符串复制到 QString 时,这发生在我身上。 The C-string wasn't '\\0'-terminated which caused the strlen function to return a value in the billions. C 字符串不是以 '\\0' 结尾的,这导致strlen函数返回数十亿的值。 So then it attempted to allocate several GB of RAM, which caused the bad_alloc .然后它尝试分配几 GB 的 RAM,这导致了bad_alloc

I have also seen bad_alloc when I accidentally accessed an uninitialized variable in the initializer-list of a constructor.当我不小心访问构造函数的初始化列表中的未初始化变量时,我也看到了bad_alloc I had a class foo with a member T bar .我有一个带有成员T bar的类foo In the constructor I wanted to initialize the member with a value from a parameter:在构造函数中,我想用参数中的值初始化成员:

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

Because I had mistyped the parameter, the constructor initialized bar with itself (so it read an uninitialized value!) instead of the parameter.因为我输错了参数,构造函数用它自己初始化了 bar(所以它读取了一个未初始化的值!)而不是参数。

valgrind can be very helpful with such errors! valgrind 对此类错误非常有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM