如何在C++中捕获内存不足异常?

49

请问如何捕获内存不足异常?

例如:

try
{
    while(true)
    {
        int i = new int;
    }
}
catch( ? <--- what should be put here?)
{
    //exception handling
}

还有这个也是,

queue<int> q;
try
{
     while(true)
     {
          q.push(10);
     }
}
catch( ? <---- what should be put here?)
{
     //error handling
}

2
@MooingDuck 我认为对于那个问题的确切答案,下面的回答比那个问题的回答更适合所有有这个问题的人。 - Andy
4个回答

64

捕获 std::bad_alloc 异常。

你还需要一个处理错误的策略,因为许多想要做的事情都需要内存(即使只是在关闭前向用户显示错误)。一种策略是在启动时分配一块内存,并在异常处理程序中之前使用 delete 来释放它,以便有些可用于使用.


2
请注意,在实际情况下,有些内存可能已经被堆栈展开(例如本地std::string变量的销毁)释放,这发生在抛出和捕获错误之间。通常,std::bad_alloc的处理程序将相当“外部”,而内存可能会在深度嵌套的情况下耗尽。只是说像打印错误消息这样的操作不一定会失败。 - Marc van Leeuwen

29

正如其他人指出的那样,您想要捕获的是std::bad_alloc。 您还可以使用catch(...)catch(exception& ex)捕获任何异常; 后者允许读取并在异常处理程序中使用异常数据。

Mark Ransom已经指出,当程序无法再分配更多内存时,甚至打印错误消息也可能失败。 考虑以下程序:

#include <iostream>

using namespace std;

int main() {
    unsigned long long i = 0;
    try {
        while(true) {
            // Leaks memory on each iteration as there is no matching delete
            int* a = new int;
            i++;
        }
    } catch(bad_alloc& ex) {
        cerr << sizeof(int) * i << " bytes: Out of memory!";
        cin.get();
        exit(1);
    }

    return 0; // Unreachable
}

(我强烈建议将程序编译为32位,以避免在64位机器上运行系统内存不足。 32位程序无法分配超过4 GB的内存,或者在Windows上默认情况下为2 GB。)

当第一个 bad_alloc 在无限的 while 循环中被抛出时,控制流会传递到 catch 块,但程序仍然会因未处理异常而失败。为什么?还有另一个 在异常处理程序 内部抛出的 bad_alloc ,当尝试打印到 cerr 时。您可以通过使用调试器来验证这一点:在 catch(bad_alloc& ex) 行处设置断点,运行程序并逐个语句地浏览每个语句,一旦到达断点,就会抛出一个 bad_alloc 异常。

因此,要正确处理内存不足的情况,您需要保留一些内存,以便在退出之前可以打印错误消息。否则,程序将在尝试打印错误消息时崩溃,并出现未处理的异常。为此,您可以分配一个在异常处理程序中释放的内存块,如 Mark Ransom 建议的那样:

// Reserve 16K of memory that can be deleted just in case we run out of memory
char* _emergencyMemory = new char[16384];
// ...
try {
// ...
} catch(bad_alloc& ex) {
    // Delete the reserved memory so we can print an error message before exiting
    delete[] _emergencyMemory;

    cerr << sizeof(int) * i << " bytes: Out of memory!";
    cin.get();
    exit(1);
}
//...

听说过RAII吗?如果你像这样从事软件工程,就没有必要使用C++——你可以使用C。 - user8434768
我看到有人声称,预分配内存的“技巧”可以避免出现内存不足的情况。 - user8434768
你为什么要将std::bad_alloc作为一个值来捕获? - user8434768
4
已更新代码以通过引用捕获异常。然而,这段代码有意地避免使用RAII,因为它旨在演示内存泄漏。这就是答案的全部重点。 - bwDraco

15
catch (std::bad_alloc& ba){
    cerr << "bad_alloc caught: " << ba.what() << endl;
}

作为一条注意事项,你应该阅读bdonlan的评论。调用cerr有可能会失败。Mark Ransom在他的回答中提出的建议是缓解这个问题的好方法。


8
注意:由于内存不足的情况,向 cerr 输出内容可能会失败。 - bdonlan
@bdonlan:我同意你的观点。我没有考虑到那种情况,因为我太习惯于在尝试分配过大的向量时出现这个问题了;)。我已经更新了我的答案。 - GWW
@bdonlan:我评论后速度非常慢 :P - GWW

6

您需要捕获类型为std::bad_alloc的对象。

或者,您也可以使用newnothrow版本:

int *pi = new (nothrow) int[N]; 
if(pi == NULL) 
{
   std::cout << "Could not allocate memory" << std::endl;
}

当您使用此功能时,如果new失败,则不会抛出异常。相反,它只是返回NULL,您需要在继续之前进行检查。

我能否以某种方式将 nothrow 版本的 new 设置为默认值,这样我就不需要每次都写 (nothrow) 了吗? - Quest
@问题:我不知道这是否可能。也许作为最后的办法,你可以这样做:#define NEW new (nothrow) 然后 int *pi = NEW int[N]; - Nawaz
我觉得我从没在我的代码中使用过这种形式的new,成功分配内存是不可选的。 - user8434768
@quest 为 Visual C++ 链接 nothrownew.obj。 - user3161924
1
@user8434768 也许你正在一个环境中工作,由于运行时开销或空间限制,你无法承担抛出异常的代价,因为每个可能抛出异常的函数都需要编译器生成额外的代码。 - Quest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接