使用catching std::bad_alloc的策略

13

我在开发中经常使用Qt并喜欢它。Qt对象的通常设计模式是使用new进行分配。

几乎所有的例子(尤其是由Qt设计器生成的代码)都没有对std::bad_alloc异常进行检查。由于已分配的对象(通常是小部件等)很小,因此这几乎从不是问题。毕竟,如果您无法分配大约20个字节之类的东西,则几乎没有什么方法可以解决该问题。

目前,我采取了一个策略,即在try/catch中包装“大”(大小超过一页或两页)的分配。如果失败,则向用户显示消息。对于较小的分配,我将允许应用程序崩溃并出现std::bad_alloc异常。

所以,我想知道这种情况下的思路是什么?

每次new操作都进行检查好吗?还是只有我预计可能会失败的操作需要检查?

此外,在处理资源更受限制的嵌入式环境时,情况显然完全不同。我在桌面应用程序的上下文中提问,但也会感兴趣其他场景的答案。

6个回答

19
问题不在于“在哪里捕获异常”,而在于“捕获异常后要做什么”。如果您想进行检查,最好使用而不是用try catch包装。
    #include <new>
    x = new (std::nothrow) X();
    if (x == NULL) {
        // allocation failed
    }

我的通常做法是:

  • 在非交互式程序中,在主要级别捕获并在那里显示足够的错误信息。

  • 在具有用户交互循环的程序中,也在循环中捕获,以便用户可以关闭某些内容并尝试继续。

例外情况下,还有其他一些地方适合使用捕获,但这种情况很少见。


1
确实,nothrow new是一个可行的选择,但如果在给定的代码块中有几个分配,那么使用try/catch块比使用nothrow new要冗长得多。我完全同意“该怎么做”才是真正的问题,因为内存不足的情况确实限制了你可以做多少来修复问题。 - Evan Teran
1
不是那么关于“该做什么”,而是“你是否能做任何事情”。 - jalf
如果你在足够高的位置捕获它,堆栈展开期间可能会释放一些内存。你可以通过使用new_handler或类似int flag = false; try { vector<int> v(1000); flag = true; doEventLoop(); } catch(bad_alloc) { if (flag) cout << "我有4k可用来提示用户\n"; }的技巧来确保某些东西被释放。 - Steve Jessop
“提示用户”可能意味着弹出一个内存不足对话框和/或保存提示。当然,在Linux上,您不会看到bad_alloc,您只会得到页面错误。在任何操作系统上,您可能会发现在应用程序看到内存不足之前它会停滞不前,并且/或者由于UI和内核无法分配内存,您的进程中有内存也无法帮助您显示对话框、保存文件等等。但是,尝试一下可能是值得的(也许因为您失败的分配是一个大对象,所以仍然有空闲内存)。 - Steve Jessop
@onebyone,关于Linux,您是在暗示可以通过调整/proc/sys/vm/overcommit*或其他方式来修改行为吗?(那个默认设置很愚蠢,应该是预处理配置而不是系统范围的)。 - AProgrammer
1
是的,我指的是超额承诺。我不知道您的Linux进程由于内存耗尽而崩溃的其他具体原因。正如我所说,无论在任何操作系统上,当内存用尽时都存在某种风险,即使只是其他应用程序也会出现问题。因此,您可以尽力而为,但不能指望这实际上能使用户的情况得到恢复。 - Steve Jessop

12

当你可以处理异常时,请处理异常。如果分配失败,而您的应用程序无法在没有那部分内存的情况下继续运行,请何必检查错误呢?

当有意义的恢复方式时,可以处理错误。如果您对错误无能为力,就让其传播。


我想接受你和A程序员的回答,它们似乎都很好。由于A程序员先回答,我选择他的回答。 - Evan Teran
是的是的。他的更加详细。 - jalf

6
我通常会在用户启动操作的那一点捕获异常。 对于控制台应用程序,这意味着在main中,对于GUI应用程序,我将处理程序放置在按钮单击处理程序等位置。
我认为在操作过程中捕获异常没有意义,因为用户通常希望操作要么成功,要么完全失败。

4

这是一个比较旧的帖子,但是当我在2012年搜索“新/删除覆盖时考虑std :: bad_alloc”时,它确实出现了。

我不认为“哦,无论如何你也没有办法”这个概念是可行的选择。 我个人在自己的分配中使用“if(alloc()){} else {error / handling}”的方式。这样,我可以适当地处理和/或报告每个情况的有意义的上下文。

现在,其他可能的解决方案包括: 1)覆盖应用程序的新/删除,其中您可以添加自己的内存不足处理。

尽管像其他海报所说,特别是没有特定上下文的知识,主要选项可能只是关闭应用程序。 如果是这种情况,您将希望您的处理程序预先分配其所需的内存,并且/或者使用静态内存,因此希望处理程序本身不会耗尽。

在这里,您至少可以弹出对话框并说一些类似以下的内容: “应用程序已经用完内存。这是一个致命错误,现在必须自我终止。 应用程序必须在最小系统内存要求下运行。向xxxx发送调试报告”。 处理程序还可以保存正在进行的任何工作等,适合应用程序。

无论如何,您都不希望将其用于诸如(警告,业余幽默)航天飞机、心率监测器、肾透析机等关键问题。 当然,这些事情需要更加健壮的解决方案,使用故障安全措施、紧急垃圾收集方法、100%测试/调试/模糊等。

2)与第一个类似,使用自己的处理程序设置全局“set_new_handler()”,以在全局范围内捕获内存不足条件。 至少可以处理#1中提到的内容。


1
实际问题是你是否应该捕获std::bad_alloc异常? 在大多数情况下,如果你的内存用尽了,你无论如何都会失败,并且可能需要结束你的程序。

0

main()中处理它(或在Qt中等效的顶层异常处理程序)

原因是std::bad_alloc可能会在你耗尽内存空间(32位系统上为2或3 GB,在64位系统上不会发生),或者在耗尽交换空间时发生。现代堆分配器并没有调整为从交换空间运行,因此这将是一种缓慢、嘈杂的死亡——很有可能您的用户会在应用程序不再响应时杀死它。而且在Linux上,默认情况下OS内存处理很差,你的应用程序很可能会被自动杀死。

所以,你能做的很少。承认你有一个bug,并查看是否可以保存用户可能已经完成的任何工作。为了能够这样做,最好尽可能地中止。是的,这实际上可能会失去一些最后的用户输入。但正是这个行动很可能触发了OOM情况。目标是保存任何你能够信任的数据。


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