在C/C++中处理内存不足情况的优雅方法是什么?

Mat*_*att 45 c c++ linux

我正在编写一个消耗大量内存的缓存应用程序.

希望我能够很好地管理自己的记忆,但是我只是在考虑如果我的内存不足会怎么做.

如果调用甚至分配一个简单的对象失败,即使是syslog调用也可能会失败?

编辑:好的,也许我应该澄清这个问题.如果malloc或new返回NULL或0L值,那么它实质上意味着调用失败,并且由于某种原因它无法为您提供内存.那么,在这种情况下做什么是明智的做法?

EDIT2:我刚刚意识到对"new"的调用会引发异常.这可以在更高的水平上捕获,所以我可以优雅地进一步退出.此时,甚至可以根据释放的内存量进行恢复.至少我应该在那一点上希望能够记录一些东西.因此,虽然我已经看到在新的之后检查指针值的代码,但这是不必要的.在C中,您应该检查malloc的返回值.

Bil*_*eal 18

好吧,如果您遇到无法分配内存的情况,那么您将获得std::bad_alloc异常.该异常导致程序堆栈被解除.很可能,应用程序逻辑的内部循环不会处理内存不足的情况,只有更高级别的应用程序应该这样做.因为堆栈正在解开,所以很大一部分内存将被释放 - 实际上应该是程序使用的几乎所有内存.

一个例外是当你要求一个非常大(例如几百MB)的内存块时,这是无法满足的.但是,当发生这种情况时,通常会留下足够小的内存块,这样您就可以优雅地处理故障.

堆栈展开是你的朋友;)

编辑:刚刚意识到问题也被标记为C - 如果是这样的话,那么当发现内存条件不足时,你应该让你的函数手动释放它们的内部结构; 不这样做是内存泄漏.

EDIT2:示例:

#include <iostream>
#include <vector>

void DoStuff()
{
    std::vector<int> data;
    //insert a whole crapload of stuff into data here.
    //Assume std::vector::push_back does the actual throwing
    //i.e. data.resize(SOME_LARGE_VALUE_HERE);
}

int main()
{
    try
    {
        DoStuff();
        return 0;
    }
    catch (const std::bad_alloc& ex)
    {   //Observe that the local variable `data` no longer exists here.
        std::cerr << "Oops. Looks like you need to use a 64 bit system (or "
                     "get a bigger hard disk) for that calculation!";
        return -1;
    }
}
Run Code Online (Sandbox Code Playgroud)

EDIT3:好的,根据评论者的说法,有些系统在这方面没有遵循标准.另一方面,在这样的系统上,无论如何你都将成为SOL,所以我不明白他们为什么值得讨论.但是,如果你这样的平台上,它是值得牢记.

  • 如果你打算回答,请...*请*评论为什么你downvoted.我厌倦了无法改善答案,因为人们不会告诉我他们不喜欢什么. (19认同)
  • @Billy:不是因为它的OOM杀手而臭名昭着的Linux?实现不仅仅是编译器,它是库和主机环境.通常我同意我们不应该迎合非标准的实现,但是根据我们别无选择的标准,内存很少被占用. (6认同)
  • 相关:[To New,Perchance to Throw,Part 2](http://www.gotw.ca/publications/mill16.htm). (5认同)
  • @stepancheg:你正在弄乱整张照片.Linux*可以遵循"乐观"内存分配方案,在该方案中,它将分配给它在分配时可能没有的进程内存.分配*仍然可以被拒绝*,并且在"malloc"的情况下返回"NULL",或者在C++的"new"的情况下抛出异常,*正如标准所说的那样*.如果以后无法满足乐观分配,Linux将通过OOM杀手终止该过程.我非常怀疑它是通过发送SIGSEGV来做到这一点 - 我会期待更严重的事情. (5认同)
  • 无法保证会抛出bad_alloc.malloc impl只能mmap匿名(实际上它确实如此),并且尝试写入分配的区域将导致segv,而不是异常. (4认同)
  • @stepancheg:任何执行此操作的系统都不符合C或C++标准.根据这两个标准,`malloc`必须在失败时返回`NULL`.如果您的系统不这样做,那么该系统不能声称使用符合标准的C或C++实现.当然,如果`malloc`返回`NULL`并且您尝试取消引用指针,则最终会出现未定义的行为域(在POSIX系统上将是`SIGSEGV`,在Windows系统上将是`EXCEPTION_ACCESS_VIOLATION`. (3认同)
  • @stepancheg是正确的,存在这样的C和C++实现.它们不符合标准,但它们很常见,需要处理它们.有时没有优雅的出路,例如当OOM杀手杀死内存耗尽的第一个迹象时. (3认同)
  • @billy标准linux malloc就像我描述的那样. (3认同)
  • @billy我用ubuntu 10.04检查了我的上网本#include <stdio.h> #include <stdlib.h> int main(){printf("%d \n",malloc(700*1024*1024)!= NULL ); 返回0; 这没有失败,而顶部显示350 mb free + 50 mb缓冲区.格式丢失了,但我认为你有了这个想法. (3认同)
  • @billy我总共有1 GB的ram而且没有交换.而且,我修改了程序,所以它成功分配(没有空闲)4次700 mb(循环).它确实比系统具有更多的内存.for(i = 0; i <100; ++ i)printf("%d \n",malloc(700*1024*1024)!= NULL); (3认同)
  • @billy c和c ++之间没有区别:segv不会在malloc/new上发送,而是在你访问分配区域时发送. (2认同)
  • ......所以,[引证需要].可以通过`/ proc`中的标志关闭整个行为 (2认同)
  • @Thanatos看起来你是对的,oom杀手只是杀了一个进程,而不是发出信号 (2认同)
  • ...而且我要补充的是,即使在比利更喜欢的平台上,一个故意使用尽可能多的内存的应用程序(或者,等效地,它具有稳定的内存泄漏)已经是各种各样的反社交.在桌面操作系统上,它很可能最终通过一种或另一种方式退出或强行杀死,即使只是用户试图阻止操作系统停止或侧面弯曲.在更受限制的嵌入式环境中,您有时会发现尽管分配失败,一切都能正常工作. (2认同)
  • @Steve:+1发表评论.也就是说,每当我看到这种类型的内存情况时,就会发生一个设计设计不合理的系统只是用来做大事的事情.例如,几百MB的Excel工作簿.当然,Excel是内置的,但是当你开始使用它来运行在快速机器上花费数小时的计算时,它会使用大量的RAM.(好吧,你不应该使用Excel进行这些类型的计算,但许多企业*会*将它用于这些类型的工作.让操作系统随机终止Excel将毁掉他们的一整天) (2认同)

Ara*_*ion 18

这个问题不会对过度使用的内存做出假设吗?

即,内存不足的情况可能无法恢复!即使您没有剩余内存,malloc在程序尝试使用内存之前,调用和其他分配器仍可能成功.然后,BAM!,一些进程被内核杀死以满足内存负载.

  • @Daniel:请查看"OOM杀手".这是在低(虚拟)内存条件下杀死低优先级进程的事情.并非所有操作系统都有它们,但有些非常常见. (5认同)
  • @Billy @Daniel实际上它可能,所以这些downvotes是不应该的.至少在许多Linux发行版中,默认情况下会启用内存过量提交.当内存过量提交打开时,内核将始终在所有分配上返回成功,并且只会在进程尝试写入时尝试实际保留内存.如果没有可用的,则调用臭名昭着的OOM杀手并随机进程死亡(根据其OOM分数).我认为这种情况严重受损,但很多人(包括Linus)为其辩护并说在用户应用程序中优雅地处理OOM是浪费时间. (5认同)
  • @Billy(第一条评论):如果无法分配地址空间,malloc将返回NULL.但是地址空间的可用性和内存的可用性是截然不同的事情. (5认同)
  • @Billy:那么标准需要不间断电源以及完全无差错的存储器.实际上,由于C标准库无法控制的各种原因,如果没有明确地解除分配,内存就会变得无法访问. (4认同)
  • @Alex:我认为"可能"夸大了一点 - 这就是他们所做的,但据我所知,这是标准所不允许的.但是编译器和操作系统有很多东西会破坏标准 - 只要它们具有兼容的模式,那么它们就是合法的.如果您想要一个兼容的环境,可以禁用过度提交,尽管这需要程序员和用户之间不切实际的通信量.在实践中,您可以忽略Linus并正确处理OOM,但必须接受操作系统可以随时因任何原因(包括OOM)终止您的应用程序. (3认同)
  • 如果没有剩余内存,则需要调用malloc**以根据C和C++标准返回"NULL"指针. (2认同)
  • @Ben:这似乎与Linux有关.这并不意味着Linux正在做的事情符合标准. (2认同)
  • "标准"(我认为这意味着C99)在哪里需要这个?它仅指分配的"空间",而不是内存,因此它似乎可以被解释为"地址空间". (2认同)

小智 6

我在Linux上没有任何特定的经验,但我花了很多时间在游戏机上进行视频游戏,其中内存耗尽,以及基于Windows的工具.

在现代操作系统上,您最有可能耗尽地址空间.因此,内存耗尽基本上是不可能的.因此,只需在启动时分配一个大缓冲区或缓冲区,以便保存您需要的所有数据,同时为操作系统留下少量数据.将随机垃圾写入这些区域可能是一个好主意,以迫使操作系统实际将内存分配给您的进程.如果你的过程幸存下来,试图使用它所要求的每个字节,那么现在有一些支持保留给所有这些东西,所以现在你是金色的.

写/窃取您自己的内存管理器,并指示它从这些缓冲区分配.然后,在您的应用中始终如一地使用它,或利用gcc的--wrap选项malloc适当地转发来自和朋友的电话.如果您使用任何无法定向的图书馆来打电话给您的记忆管理员,那么他们就会把它们弄得一团糟,因为它们会阻碍您.缺乏可覆盖的内存管理调用是更深层次问题的证据; 如果没有这个特定的组件,你会更好.(注意:即使您正在使用--wrap,相信我,这仍然是一个问题的证据!生命太短暂,无法使用那些不会让您的内存管理过载的库!)

一旦你的内存耗尽,好吧,你已经搞砸了,但是你之前仍然有空闲的空间,所以如果释放你所要求的一些内存太难了你可以(小心)打电话系统调用将消息写入系统日志然后终止,或者其他什么.只要确保避免调用C库,因为他们可能会在你最不期望的时候尝试分配一些内存 - 使用具有虚拟化地址空间的系统的程序员因这种事情而臭名昭着 - 这就是一开始就引起了这个问题的事情.

这种方法听起来像屁股上的痛苦.嗯......是的.但它很简单,值得投入一些努力.我认为有关于此的Kernighan和/或Ritche报价.


gav*_*inb 5

如果您的应用程序可能会分配大块内存并面临达到每个进程或 VM 限制的风险,那么等待分配实际上失败是一种很难恢复的情况。到mallocreturnNULLnewthrows 时std::bad_alloc,事情可能已经太远而无法可靠地恢复。根据您的恢复策略,许多操作本身可能仍需要堆分配,因此您必须非常小心可以依赖哪些例程。

您可能希望考虑的另一个策略是查询操作系统并监控可用内存,主动管理您的分配。如果您知道它可能会失败,则可以通过这种方式避免分配大块,从而有更好的恢复机会。

此外,根据您的内存使用模式,使用自定义分配器可能会比标准的内置malloc. 例如,随着时间的推移,某些分配模式实际上会导致内存碎片,因此即使您有空闲内存,堆领域中的可用块也可能没有合适大小的可用块。这方面的一个很好的例子是 Firefox,它切换到dmalloc并看到内存效率有了很大的提高。

  • 同意 - 而不是试图继续使用内存直到没有剩余内存,您应该提供一个配置选项来设置您的进程将用于缓存的最大内存量。当你去缓存一个新对象时,检查你不会超过内存限制 - 如果你会,扔掉一些旧对象。换句话说,将整个问题交给系统管理员。 (2认同)
  • `“一个很好的例子是 Firefox,它切换到 dmalloc 并看到内存效率大大提高。”` 你知道我们可以在哪里阅读更多相关信息吗? (2认同)