我是否应该在我的C代码中检测OOM(内存不足)错误?

cdl*_*ary 24 c malloc memory-management

我已经投入了大量的C代码行来清理标签/条件失败的内存分配(由alloc系列返回指示NULL).我被告知这是一个很好的做法,因此,在内存故障时,可以标记适当的错误状态,并且调用者可以执行"优雅的内存清理"并重试.我现在对这个我希望清理的哲学有些怀疑.

我想这可能是一个主叫方可能会取消分配过多的缓冲空间或剥夺他们的数据关系的对象,但我发现来电者很少有能力(或者是抽象的适当的水平),这样做.此外,从被叫函数提前返回没有副作用通常是非常重要的.

我刚刚发现了Linux OOM杀手,这似乎使我的主要开发平台上的这些努力毫无意义.

默认情况下,Linux遵循乐观的内存分配策略.这意味着当malloc()返回非NULL时,无法保证内存确实可用.这是一个非常糟糕的错误.如果事实证明系统内存不足,臭名昭着的OOM杀手就会杀死一个或多个进程.

我认为可能还有其他平台遵循相同的原则.有没有务实的东西使得检查OOM条件值得?

小智 20

如果用户或系统管理员限制(参见ulimit)进程的内存空间,或者操作系统支持每个用户的内存分配限制,即使在具有大量内存的现代计算机上也可能发生内存不足的情况.在病理情况下,碎片化甚至可能使这种情况变得相当可能.

但是,由于动态分配内存的使用在现代程序中很普遍,出于好的理由,处理内存不足错误变得非常繁琐.必须以高成本的复杂性在任何地方进行这种错误的检查和处理.

我发现设计程序更好,以便随时崩溃.例如,确保用户创建的数据始终保存在磁盘上,即使用户没有明确保存它也是如此.(例如,请参见vi -r.)这样,您可以创建一个函数来分配在出现错误时终止程序的内存.由于您的应用程序旨在随时处理崩溃,因此崩溃是可以的.用户会感到惊讶,但不会失去(很多)工作.

永不失败的分配函数可能是这样的(未经测试的,未编译的代码,仅用于演示目的):

/* Callback function so application can do some emergency saving if it wants to. */
static void (*safe_malloc_callback)(int error_number, size_t requested);

void safe_malloc_set_callback(void (*callback)(int, size_t))
{
    safe_malloc_callback = callback;
}

void *safe_malloc(size_t n)
{
    void *p;

    if (n == 0)
        n = 1; /* malloc(0) is not well defined. */
    p = malloc(n);
    if (p == NULL) {
        if (safe_malloc_callback)
            safe_malloc_callback(errno, n);
        exit(EXIT_FAILURE);
    }
    return p;
}
Run Code Online (Sandbox Code Playgroud)

Valerie Aurora的文章Crash-only软件可能很有启发性.


Cha*_*tin 14

看一下问题的另一面:如果你是malloc内存,它会失败,你没有 在malloc上检测到它,你什么时候检测到它?

显然,当您尝试取消引用指针时.

你怎么会发现它?通过获得Bus error类似的东西,在malloc之后的某个地方,您必须使用核心转储和调试器进行跟踪.

另一方面,你可以写

  #define OOM 42 /* just some number */

  /* ... */

  if((ptr=malloc(size))==NULL){
      /* a well-behaved fprintf should NOT malloc, so it can be used
       * in this sort of context
       */
      fprintf(stderr,"OOM at %s: %s\n", __FILE__, __LINE__);
      exit(OOM);
   }
Run Code Online (Sandbox Code Playgroud)

并获得"解析器的OOM:447".

你选.

更新

关于优雅回归的好问题.确保优雅回报的困难在于,一般来说,你真的无法建立一种范式或模式,尤其是在C中,这毕竟是一种奇特的汇编语言.在垃圾收集环境中,您可以强制GC; 在具有异常的语言中,您可以抛出异常并展开事物.在C中你必须自己做,所以你必须决定你想要投入多少努力.

大多数程序中,异常终止是您可以做的最好的事情.在这个方案中,你(希望)在stderr上获得一个有用的消息 - 当然它也可以是记录器或类似的东西 - 并且已知值作为返回码.

具有较短恢复时间的高可靠性程序会使您进入恢复模块之类的程序,您可以在其中编写试图使系统恢复到可生存状态的代码.这些很棒但很复杂; 我链接的论文详细讨论了它们.

在中间,您可以提出一个更复杂的内存管理方案,比如管理自己的动态内存池 - 毕竟,如果其他人可以编写malloc,那么你也可以.

但是,没有一般的模式(我知道无论如何)清理足够能够可靠地返回并让周围的程序继续.

  • 并且您可以使用宏和/或包装函数来使其更容易. (2认同)

ste*_*sch 8

无论平台(可能是嵌入式系统除外),最好先检查NULL然后退出,而不要手动进行任何(或多次)清理.

内存不足不是一个简单的错误.这对今天的系统来说是一场灾难.

" 编程实践"(Brian W. Kernighan和Rob Pike,1999)一书定义emalloc()了如果没有内存就会退出并显示错误消息的函数.


Art*_*ius 6

这取决于你写的是什么.它是一个通用的图书馆吗?如果是这样,您希望尽可能优雅地处理内存不足,特别是如果期望它将用于el-cheapo系统或嵌入式设备是合理的.

考虑一下:程序员正在使用您的库.在他的程序中有一个错误(可能是未初始化的变量),它将一个愚蠢的参数传递给您的代码,因此尝试分配一个3.6GB的内存块.显然malloc()返回NULL.他宁愿在库代码中的某处生成无法解释的段错误,还是返回值来表示错误?

为了避免在代码中进行错误检查,一种方法是在开始时分配合理的内存,并根据需要进行子分配.

关于Linux OOM杀手,我听说在主要发行版上默认禁用此行为.即使它已启用,也不要错误的想法:malloc() 可以返回NULL,如果你的程序的总内存使用量超过4GiB(在32位系统上),它肯定会.换句话说,即使malloc()实际上没有为您提供一些RAM /交换空间,它也会保留部分地址空间.