【问题标题】:Can the C++ `new` operator ever throw an exception in real life?C++ `new` 操作符在现实生活中会抛出异常吗?
【发布时间】:2011-01-30 15:04:38
【问题描述】:

new 操作符在现实生活中可以抛出异常吗?

如果是这样,除了杀死我的应用程序之外,我还有其他处理此类异常的选项吗?

更新:

是否有任何现实世界的new-heavy 应用程序检查故障并在没有内存时恢复?


另见:

【问题讨论】:

  • 呃,你能改一下你的问题吗?
  • 我必须在任何地方检查来自new 的异常,即使在使用小分配时也是如此?如果小尺寸的new 失败,则意味着我不能分配 10s 的字节,甚至不能用 throw new MyApplicationNeedMoreMemory 重新抛出一些特定于应用程序的异常
  • 好吧,你不应该将new 与 throw 一起使用......正如其他人所提到的,除非你有处理它的策略,否则你不应该捕获异常。
  • “恢复”是什么意思?向用户提供连贯的错误消息是有好处的。但是,内存不足错误通常源于程序错误或大型数据集,在任何一种情况下,添加更多内存都可能是不够的(在错误情况下,添加的内存量可能无济于事)。
  • @osgx:当然;我应该说,无论是给用户还是日志,提供连贯的错误消息而不是崩溃是有好处的。

标签: c++ exception new-operator out-of-memory


【解决方案1】:

如果您在运行 Linux 且没有虚拟内存的典型嵌入式处理器上运行,那么如果您分配的内存过多,您的进程很可能会在 new 失败之前被操作系统终止。

如果您在物理内存小于最大虚拟内存(标准 Windows 上为 2 GB)的计算机上运行程序,您会发现一旦分配的内存量大约等于可用物理内存,进一步分配将成功,但会导致分页到磁盘。这将使您的程序陷入困境,并且您实际上可能无法达到耗尽虚拟内存的地步。所以你可能不会抛出异常。

如果你的物理内存比虚拟内存多,并且你只是继续分配内存,当你用尽虚拟内存到无法分配你请求的块大小时,你会得到一个异常。

如果您有一个长时间运行的程序,它以多种不同的块大小(包括小块)分配和释放,并且具有多种生命周期,则虚拟内存可能会变得碎片化,以至于 new 将无法找到大块足够的块来满足请求。然后 new 会抛出异常。如果您碰巧发生内存泄漏,在随机位置泄漏偶尔的小块,最终将内存碎片化到任意小块分配失败的程度,并引发异常。

如果你有一个程序错误不小心将一个巨大的数组大小传递给 new[],new 将失败并抛出异常。例如,如果数组大小实际上是某种随机字节模式,可能会发生这种情况,可能源自未初始化的内存或损坏的通信流。

以上所有内容都是针对默认全局新建的。但是,您可以替换 global new 并且可以提供特定于类的 new。这些也可以抛出,这种情况的含义取决于您如何对其进行编程。 new 通常包含一个循环,该循环尝试所有可能的途径来获取请求的内存。当所有这些都用尽时,它会抛出。那你做什么就看你自己了。

您可以从 new 中捕获异常,并利用它提供的机会记录异常发生时的程序状态。您可以“转储核心”。如果您在程序启动时分配了一个循环检测缓冲区,则可以在终止程序之前将其转储到磁盘。程序终止可以是优雅的,与简单地不处理异常相比,这是一个优势。

我个人还没有看到异常后可以获得额外内存的示例。然而,一种可能性如下:假设您有一个高效但不擅长回收可用空间的内存分配器。例如,它可能容易出现空闲空间碎片,其中空闲块相邻但未合并。您可以使用 new_handler 中捕获的来自 new 的异常来运行压缩过程以获取可用空间,然后再重试。

严肃的程序应该将内存视为一种潜在的稀缺资源,尽可能控制其分配,监控其可用性并在出现重大问题时做出适当的反应。例如,您可以假设在任何实际程序中,传递给内存分配器的大小参数都有一个很小的上限,任何大于此值的东西都会导致某种错误处理,无论请求是否可以使满意。您可以争辩说,应该监控一个长时间运行的程序的内存增长速度,如果可以合理地预测该程序将在不久的将来耗尽可用内存,则应该开始有序地重新启动该进程。

【讨论】:

  • tl;dr(抱歉,不得不说。:-P)
  • +1 用于解释,在现实生活中,new 只会因程序员错误而抛出(操作系统总是会给你内存,除非你真的搞砸了)
【解决方案2】:

当您根据外部(例如来自用户空间、网络)给定的内容分配内存时,最好检查/捕获此异常,因为这可能意味着试图破坏您的应用程序/服务/系统,您不应该允许这种情况发生。

【讨论】:

    【解决方案3】:

    new-handler 函数是分配函数在新尝试分配内存失败时调用的函数。我们可以有自己的日志记录或一些特殊的操作,例如,g 安排更多的内存等。 其预期目的是以下三件事之一: 1) 提供更多可用内存 2) 终止程序(例如通过调用 std::terminate) 3) 抛出 std::bad_alloc 类型的异常或从 std::bad_alloc 派生的异常。 默认实现抛出 std::bad_alloc。用户可以拥有自己的新处理程序,它可能提供与默认行为不同的行为。仅在您真正需要时才应使用它。有关更多说明和默认行为,请参阅示例,

    #include <iostream>
    #include <new>
    
    void handler()
    {
        std::cout << "Memory allocation failed, terminating\n";
        std::set_new_handler(nullptr);
    }
    
    int main()
    {
        std::set_new_handler(handler);
        try {
            while (true) {
                new int[100000000ul];
            }
        } catch (const std::bad_alloc& e) {
            std::cout << e.what() << '\n';
        }
    }
    

    【讨论】:

      【解决方案4】:

      请注意,在 Windows 中,非常大的 new/mallocs 只会从虚拟内存中分配。实际上,您的机器会在您看到该异常之前崩溃。

      char *pCrashMyMachine = new char[TWO_GIGABYTES];
      

      如果你敢就试试吧!

      【讨论】:

      • 如何指定 50 TB?应用程序可以处理这种情况吗?哪些版本的 windows 会崩溃?
      • 你让我启动了编译器!我的错误——50 TB 以上无法工作。该值限制为 2^31,约 2 gigs。因此,请在剩余磁盘空间不足 2 GB 的机器上进行实验。我最初在 Windows XP 上运行它。不知道其他版本的 O/S 和 MSVC 运行时,运行起来真的很烦人。
      【解决方案5】:

      new 运算符和 new[] 运算符应该抛出 std::bad_alloc,但情况并非总是如此,因为有时可以覆盖该行为。

      一个人可以使用std::set_new_handler,突然之间会发生与抛出std::bad_alloc完全不同的事情。尽管标准要求用户要么使内存可用、中止或抛出std::bad_alloc。但当然,情况可能并非如此。

      免责声明:我不建议这样做。

      【讨论】:

      • 这就是我所说的坏习惯。
      • std::set_new_handler in &lt;new&gt; 是标准 C++,§18.4.2.2-3。例如,如果您有某种可以执行的垃圾收集,或者您想记录错误,那么使用它是一件非常合理的事情。通过throw bad_alloc 退出new_handler 也不错。
      • 也 - 标准要求用户的新处理程序使内存可用、中止或抛出 bad_alloc。
      • @Potatoswatter:非常感谢您提供的信息,更新了答案。
      • 来自cppreference: "如果失败,标准库实现会调用std::get_new_handler返回的函数指针并重复分配尝试,直到新的处理程序没有返回或变为空指针,此时时间它抛出std::bad_alloc。”几乎我正在做的事情here
      【解决方案6】:

      在 Unix 系统中,通常会在内存限制的情况下运行长时间运行的进程(使用 ulimit),这样它就不会耗尽系统的所有内存。如果您的程序达到该限制,您将获得std::bad_alloc


      OP 编辑​​更新:程序从内存不足情况中恢复的最典型情况是在垃圾收集系统中,然后执行 GC 并继续。不过,这种按需 GC 确实只是为了最后的努力。通常,好的程序会定期尝试 GC 以减少收集器的压力。

      非 GC 程序从内存不足问题中恢复的情况不太常见,但对于面向 Internet 的服务器,一种恢复方法是简单地拒绝导致内存用完的请求并发出“临时”错误。 (“先入先得”策略。)

      【讨论】:

      • 也是 GUI 应用程序。如果用户操作导致内存耗尽。然后放弃当前操作,而不是整个应用程序。
      【解决方案7】:

      是的,新的可以而且会扔。

      既然您问的是“真正的”程序:我从事各种收缩包装的商业软件应用程序已有 20 多年了。拥有数百万用户的“真实”程序。你今天可以去买现成的。是的,新的可以扔。

      有多种方法可以处理这个问题。

      首先,编写自己的 new_handler(在 new 放弃并抛出之前调用它 - 请参阅 set_new_handler() 函数)。当你的 new_handler 被调用时,看看你是否可以释放一些你并不真正需要的东西。还警告用户他们的内存不足。 (是的,如果你真的很低,就很难警告用户任何事情)。

      一件事是在程序的开始处预先分配了一些“额外”内存。当内存不足时,请使用此额外内存来帮助将用户文档的副本保存到磁盘。然后发出警告,然后优雅地退出。

      等等。这只是一个概述,显然还有更多内容。

      处理低内存并不容易。

      【讨论】:

        【解决方案8】:

        在许多情况下,内存不足的情况无法进行合理的恢复,在这种情况下,让应用程序终止可能是完全合理的。您可能希望在较高级别捕获异常以显示比编译器默认提供的更好的错误消息,但您可能必须玩一些技巧才能使其正常工作(因为该过程可能非常低资源)。

        除非您有可以处理和恢复的特殊情况,否则可能没有理由花费大量精力尝试处理异常。

        【讨论】:

          【解决方案9】:

          osgx说:

          是否有任何实际应用 检查很多新闻并且可以 没有记忆就恢复?

          我之前已经在my answerthis question 中回答过这个问题,引用如下:

          这很难处理 种情况。你可能想要 向用户返回一个有意义的错误 您的应用程序,但如果它是 内存不足引起的问题,你 甚至可能买不起 内存分配错误信息。 这有点像 catch-22 的情况 真的。

          有一个防御性编程 技术(有时称为记忆 降落伞或雨天基金)你在哪里 当你的时候分配一块内存 应用程序启动。当你那时 处理 bad_alloc 异常,你 释放此内存,并使用 可用内存关闭 优雅地应用,包括 向 用户。这比 崩溃:)

          【讨论】:

            【解决方案10】:

            这取决于编译器/运行时以及您正在使用的operator new(例如,Visual Studio 的某些版本will not throw out of the box,但宁愿返回NULL 指针a la malloc。)

            您始终可以catch std::bad_alloc 异常,或explicitly use nothrow new 返回NULL 而不是抛出。 (另请参阅 past StackOverflow posts 围绕主题旋转。)

            请注意,operator newmalloc 一样,在内存不足、地址空间不足(例如 32 位进程中的 2-3GB 取决于操作系统)时失败),超出配额(ulimit 已经提到)或超出连续地址空间(例如碎片堆)。

            【讨论】:

            • 当它会失败时,我该怎么办??
            • @osgx:“错误”出现在 Visual C++ 6 (VS98) 和 Visual C++ 2003 上(但您可以设置编译器选项以使其具有符合标准要求的新行为)。这与其说是一个错误,不如说是出于向后兼容性目的而存在的不合规行为。
            • @Vlad 我想如果你在没有异常支持的情况下编译你的代码,它仍然会这样运行
            【解决方案11】:

            由于决定限制资源,最现实的新事物会抛出。假设这个类(可能是内存密集型)从物理池中取出内存,如果有很多对象从中取出(我们需要内存用于其他东西,如声音、纹理等),它可能会抛出而不是稍后崩溃能够分配内存就可以了。 (看起来像一个奇怪的副作用)。

            重载 new 在内存受限的设备中很有用。例如手持设备或游戏机,因为它很容易因为酷炫的效果而过火。

            【讨论】:

              【解决方案12】:

              是的,如果没有更多可用内存,new 将抛出异常,但这并不意味着您应该将每个新内存都包装在 try ... catch 中。仅当您的程序实际上可以对其执行某些操作时才捕获该异常。

              如果程序不能做任何事情来处理这种异常情况,如果你的内存耗尽,通常情况下,捕获异常是没有用的。如果您唯一可以合理地做的是中止程序,您也可以让异常冒泡到顶层,它也会终止程序。

              【讨论】:

                【解决方案13】:

                我使用 Mac OS X,但我从未见过 malloc 返回 NULL(这意味着 C++ 中的 new 存在异常)。机器陷入困境,尽力为进程分配越来越少的内存,最后发送 SIGSTOP 并邀请用户终止进程,而不是让它们处理分配失败。

                但是,这只是一个平台。当然,有些平台会抛出默认分配器。而且,正如 Chris 所说,ulimit 可能会引入人为的约束,这样异常就会成为预期的行为。

                另外,除了默认的/malloc之外,还有分配器。如果一个类覆盖了operator new,你使用自定义参数给new(…),或者你将一个分配器对象传递给一个容器,它可能定义了自己的条件来抛出bad_alloc

                【讨论】:

                • linux 在 vi​​rt 内存上没有 ulimit,据我所知,将允许在 mallocs/new(内部 mmaps/sbrk)上分配内存。但是当我尝试使用它时,进程(有时它可能是一个随机进程)将被 Out-Of-Memory 杀手杀死,没有任何恢复的机会,也没有转储/保存状态。
                • @osgx:不幸的是,没有更好的方法来处理过度使用。它或多或少定义为抑制分配错误,作为一项功能。您是否尝试为内存不足的情况安装信号处理程序?
                • 我查了谷歌,看起来/proc/sys/vm/overcommit_memory 可以帮助你关闭过度使用,如果你想要的话。
                • 是的,然后您将获得分配失败异常。我以这种方式运行了我的旧 Linux 笔记本电脑一段时间,它的行为主要类似于拥有 OOM Killer,因为处理它的应用程序并不多。
                【解决方案14】:

                您不需要在每个new 中处理异常:) 异常可以传播。设计您的代码,以便在每个“模块”中处理该错误的某些点。

                【讨论】:

                  【解决方案15】:

                  是的,new 可以抛出 std::bad_allocstd::exception 的子类),您可以捕获它。

                  如果你绝对想避免这个异常,而是准备测试new 的结果是否为空指针,你可以添加一个nothrow 参数:

                  T* p = new (nothrow) T(...);
                  if (p == 0)
                  {
                      // Do something about the bad allocation!
                  }
                  else
                  {
                      // Here you may use p.
                  }
                  

                  【讨论】:

                  • 我看到很多代码错误地假设 new(不带参数)在失败时返回 NULL。
                  • 所以我每次使用new时都必须检查这个NULL吗?
                  • @osgx:仅当您使用 nothrow 选项时。你有没有阅读 squelart 的回答?
                  • 是的。我需要在使用nothrow 时检查NULL,或者能够在我使用新的任何地方捕获bad_alloc?大型程序中有成千上万个这样的地方,而且可能非常困难。
                  • nothrow new 的合法用途很少。想到的两个是在处理遗留代码时(假设新代码在失败时返回 null)或当异常被禁止时(例如在嵌入式系统中)。
                  【解决方案16】:

                  当池中没有足够的可用内存来满足运行时请求时,new 运算符将抛出 std::bad_alloc 异常。

                  这可能发生在糟糕的设计或分配的内存未正确释放时。

                  此类异常的处理基于您的设计,一种方法是暂停并稍后重试,希望更多的内存返回到池中并且请求可能成功。

                  【讨论】:

                  • 如果您的数据集恰好很大,也可能发生这种情况。例如,在某些情况下,如果您尝试将 20GB 的文件通过管道传输到标准输入,那么程序无法处理请求并不是不合理的。
                  • 通过标准输入管道传输 20GB 不是很困难的情况。我已经做了很多这样大小的 grep :)
                  • 那又怎样? grep 适合流式传输。不过,祝您在 20GB 标准输入中找到最大的重复字符串。
                  【解决方案17】:

                  new 运算符将在内存不足时抛出std::bad_alloc 异常(准确地说是虚拟内存)。

                  如果new 抛出异常则为严重错误:

                  • 分配的可用 VM 多于可用(最终失败)。您可以尝试通过捕获std::bad_alloc 异常来减少内存量而不是退出程序。

                  【讨论】:

                    【解决方案18】:

                    是的,new 可以并且会在分配失败时抛出。如果内存不足或尝试分配的内存块太大,就会发生这种情况。

                    您可以捕获std::bad_alloc 异常并适当地处理它。有时这是有道理的,其他时候(阅读:大多数时候)它没有。例如,如果您尝试分配一个巨大的缓冲区但可以使用更少的空间,您可以尝试分配连续更小的块。

                    【讨论】:

                    • 写C的时候,有没有检查malloc是否返回NULL?如果没有,我怀疑我能否说服您注意新的例外情况。
                    • 您应该在任何可以合理恢复的地方捕获std::bad_alloc。在大多数情况下,您无能为力,因此最好的办法可能是在main 中捕获它,并至少给用户一个友好的错误消息或记录故障(许多专家,包括 Herb Sutter,同意这一点:gotw.ca/publications/mill16.htm)。
                    • @osgx:在 GNU 程序中,约定是调用(malloc 成功或死亡)函数xmalloc
                    • 天啊!你不检查 fclose() 的返回值?!
                    • 我做过一些嵌入式系统开发,我可以肯定地说,如果你不检查 new/malloc 操作是否成功,那么某个地方的某个用户会找到一种方法来填充如果代码缺乏适当的检查,您的设备会使您的应用程序崩溃。不检查函数的返回是不好的坏习惯。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-02-20
                    • 2012-07-21
                    • 2018-03-25
                    • 1970-01-01
                    • 2021-03-04
                    相关资源
                    最近更新 更多