由于内存导致重新分配失败时如何处理?

Posted

技术标签:

【中文标题】由于内存导致重新分配失败时如何处理?【英文标题】:How to handle realloc when it fails due to memory? 【发布时间】:2010-12-31 11:42:32 【问题描述】:

问题说明了一切,但这里是一个例子:

typedef struct mutable_t
    int count, max;
    void **data;
 mutable_t;


void pushMutable(mutable_t *m, void *object)

    if(m->count == m->max)
        m->max *= 2;
        m->data = realloc(m->data, m->max * sizeof(void*));
    
    // how to handle oom??
    m->data[m->count++] = object;

如何处理内存不足而不是 NULL 的所有数据?

edit - 让我们假设有一些事情可以做,例如在某处释放一些内存或至少告诉用户“你不能这样做 - 你内存不足”。理想情况下,我想把分配的东西留在那里。

【问题讨论】:

高度依赖应用程序...但可以肯定的是,OOM​​ 非常关键。 相关:***.com/questions/1941323/… 只是在这里添加几个答案,关于如何处理失败的realloc()(在您的情况下)的想法是执行m->max /= 4; m->max *= 3; 并尝试再次调用realloc() 以查看是否我们仍然可以挤出更多的字节。你甚至可以尝试几次,尺寸逐渐变小,但在某些时候它是不值得的。 if (!m->data) log("请升级到64位版本");中止(); 【参考方案1】:

我遇到了这个问题。配置是OS:win7(64);IDE:vs2013;Debug(Win32)。 当我的 realloc 由于内存而返回 null 时,我有两种解决方案:

1.更改项目的属性,启用大地址。 2.将我的解决方案平台从Win32改为x64。

【讨论】:

【参考方案2】:

realloc() 失败时的处理策略取决于您的应用程序。这个问题太笼统,无法回答所有可能的情况。

其他一些注意事项:

永远不要这样做:

a = realloc(a, size);

如果realloc()失败,你会丢失原来的指针,而realloc()没有free()原来的内存,所以你会得到内存泄漏。相反,这样做:

tmp = realloc(a, size);
if (tmp)
    a = tmp;
else
    /* handle error */

我想说的第二点是次要的,可能不是那么重要,但无论如何了解它是件好事:将要分配的内存增加一个因子f 是好的。假设您首先 malloc() n 个字节。然后你需要更多的内存,所以你realloc() 的大小为 n×f。然后你需要更多的内存,所以你需要 n×f2 个字节。如果您希望realloc() 使用前两个内存块的空间,您需要确保 n×f2 ≤ n + n×f。求解这个方程,我们得到 f≤ (sqrt(5)+1)/2 = 1.618 (Golden ratio)。我大部分时间都使用1.5 的系数。

【讨论】:

你有更多关于内存分配算法的资料吗? 您是否面临尝试大量但不需要的分配的风险?我有几个带有10^9 元素的数组,我可能需要realloc 其中两个。代码已经占用了 10% 的内存,恐怕 realloc 会失败。我在想realloc(old_size + 1000),但我知道,一般来说,这可能会导致许多调用 realloc。那会很糟糕吗? (现在不应该是我的情况,而是将来......)【参考方案3】:

    了解应用程序框架如何处理 OOM。许多人根本不会处理OOM。大多数情况下,一个框架在没有空闲 RAM 的情况下无法正常运行,除非它在某个地方非常清楚明确地表明它会。如果框架不能处理 OOM 并且是多线程的(现在很多),那么在很多情况下,OOM 将成为该过程的结束。即使它不是多线程的,它仍然可能接近崩溃。您是否退出流程或框架可能是一个有争议的问题;可预测的立即退出可能比在不久的将来某个半随机点的崩溃要好一点。

    如果您使用单独的专用子内存池(即不是您通常使用的 malloc)来执行一组明确定义的操作,这些操作仅受 OOM 的内存使用限制(即当前操作是在 OOM 上完全回滚或中止子内存池,而不是整个进程或主内存池),并且该子池也未被应用程序框架使用,或者如果您的框架和其余部分的整个应用程序旨在保持有意义的状态并在无空闲 RAM 条件下继续运行(在内核模式和某些类型的系统编程中很少见但并非闻所未闻),您可能是正确的返回错误代码而不是使进程崩溃。

    理想情况下,一个处理的大部分内存分配(或者更理想的是所有分配)应该在处理过程中尽快分配,最好是在它正确开始之前,最大限度地减少数据完整性丢失和/或失败时所需的回滚编码量的问题。在实践中,很多时候,为了节省项目的编程成本和时间,保持数据完整性,应用程序依赖于数据库事务,并要求用户/支持人员检测 GUI 崩溃(或服务器崩溃)并在退出时重新启动应用程序内存错误发生,而不是被写入以最佳方式应对和回滚任何和所有潜在的 OOM 情况。然后,努力尝试限制应用在过载情况下的暴露,这可能包括额外的验证和对数据大小以及同时连接和查询的限制。

    即使您检查报告有多少内存可用,其他代码通常也可能像您一样分配或释放内存,从而更改内存检查的基础并可能导致 OOM。因此,在分配之前检查可用的空闲 RAM 通常不是确保应用程序在可用 RAM 限制内运行并在足够的时间内保持数据完整性以满足用户的问题的可靠解决方案。

    最好的情况是了解您的应用程序在所有可能的情况下需要多少内存,包括任何框架开销,并将该数字保持在您的应用程序可用的 RAM 量内,但系统通常如此复杂的外部依赖关系决定了数据大小,因此实现这一点可能是不现实的。

当然,严峻的考验是,您是否能够通过较长的正常运行时间以及罕见的数据损坏、丢失或崩溃来充分满足用户的需求。在某些情况下,如果应用程序崩溃时有一个监控进程来重新启动它是有用的。

关于重新分配:

检查 realloc 的返回值 - 将它放在一个临时变量中。仅在请求的新大小 > 0 时才关心它是否为 NULL。在其他情况下,将其放在您的非临时变量中:

例如

    void* temp = realloc(m->data, m->max * sizeof(void*));
    if (m->max!=0&&temp==NULL)  /* crash or return error */ 
    m->data =(void**)temp;

编辑

在 (1) 中将“大多数情况”更改为“很多情况”。

我知道您说过如果无法分配内存,则假设“可以做某事”。但是内存管理是一个非常全局的考虑因素(!)。

【讨论】:

【参考方案4】:

还有另一个可能来自 realloc 的微妙错误。来自返回的 NULL 指针的内存泄漏是众所周知的(但很少会偶然发现)。 我的程序中偶尔会发生一次来自 realloc 调用的崩溃。我有一个动态结构,可以使用类似于这个的 realloc 自动调整其大小:

m->data = realloc(m->data, m->max * sizeof(void*)); 

我犯的错误是没有检查 m->max == 0,这释放了内存区域。并从我的 m->data 指针变成了一个陈旧的指针。

我知道这有点离题,但这是我使用 realloc 时遇到的唯一真正问题。

【讨论】:

我现在(即2016年)刚刚发现的有趣的事情是我当时使用的stdlib没有正确遵循标准,因为realloc()在这种情况下需要返回NULL长度为 0 的呼叫。这首先不会触发错误。令人着迷,因为我清楚地记得那个错误,它发生在 2004 年左右,发生在一台非常旧的(当时已经是)Solaris 机器上。【参考方案5】:

使用realloc 时应遵循的第一条规则是不要将realloc 的返回值分配给您传递给它的同一个指针。这个

m->data = realloc(m->data, m->max * sizeof(void*)); 

不好。如果realloc 失败,它会返回空指针,但不会释放旧内存。上面的代码将使您的m->data 无效,而以前由m->data 指向的旧内存块很可能会成为内存泄漏(如果您没有其他对它的引用)。

realloc的返回值应该先存放在单独的指针中

void **new_data;
...
new_data = realloc(m->data, m->max * sizeof(void*)); 

然后您可以检查成功/失败并在成功的情况下更改m->data的值

if (new_data != NULL)
  m->data = new_data;
else
  /* whatever */;

【讨论】:

【参考方案6】:

标准技术是引入一个新变量来保存 realloc 的返回值。然后,只有在成功时才覆盖输入变量:

tmp = realloc(orig, newsize);
if (tmp == NULL)

    // could not realloc, but orig still valid

else

    orig = tmp;

【讨论】:

所以直到赋值才设置为NULL?很高兴知道这一点。 然后呢?您不是为了好玩而尝试增加数组的大小,实际上您需要这样做是有原因的。 @Blindy - 操作失败。根据应用程序的逻辑,由它来决定如何恢复(也许这是一台服务器,它会使一个请求失败,但继续运行其他请求)。但这看起来像是低级库代码,不应在应用程序上强制执行内存不足策略。 @Blindy - 如果调整大小较大,您可以尝试调整较小的大小,看看是否可以解决问题。否则,您可能应该打印“没有更多内存”的错误消息并退出。或者您可以返回一个错误代码,调用者可以尝试释放一些不必要的内存并在可能的情况下重试。从内存错误中恢复在某些情况下是可能的,即使在大多数情况下不太可能。 @andreaconsole - 如果 tmp 为 NULL,那么没关系(所有现代 malloc 实现中的free(NULL))。如果 tmp 不为 NULL,那么是的,您需要释放它,但您需要在正确的时间释放它。 orig = malloc(size) ... tmp = realloc(orig, newsize) if (tmp == NULL) free(orig); ...放弃... else orig = tmp; ... 免费(原版);【参考方案7】:

这完全是你的问题!以下是一些标准:

你请求那段记忆是有原因的。如果它不可用,您的程序的工作注定要失败还是可以继续做事?如果是前者,你想用错误信息终止你的程序;否则,您可以以某种方式显示错误消息并继续。

是否有可能以时间换空间?您能否使用使用较少内存的算法来回复您尝试的任何操作?这听起来需要做很多工作,但实际上,尽管最初没有足够的内存,但仍有可能继续您的程序运行。

如果没有这些数据且内存不足,您的程序会继续跛行吗?如果是这样,您应该终止并显示错误消息。杀死你的程序比盲目地继续处理不正确的数据要好得多。

【讨论】:

【参考方案8】:

这是一个热门话题,因为该主题基本上有两种思想流派

    检测 OOM,并让函数返回错误代码。 检测 OOM 并尽快使您的进程崩溃

就我个人而言,我在 #2 营地。对于非常特殊类型的应用程序,OOM 是致命的时期。诚然,完美编写的代码可以处理 OOM,但很少有人知道如何编写在没有内存的情况下安全的代码。实际去做的麻烦更少,因为它几乎不值得付出努力。

我不喜欢将错误代码传递给 OOM 的调用函数,因为这相当于告诉调用者“我失败了,你无能为力”。相反,我更喜欢快速崩溃,因此生成的转储尽可能具有指导意义。

【讨论】:

对于 OOM 故障,可能可以采取措施。数量不多,但在某些情况下是可能的。 (在大多数应用程序中,malloc()realloc() 应该有一个包装器,它只会在内存故障时出现错误消息而退出,但对于少数具有更好解决方案的应用程序,它们不会这样做)。 @Chris,当然是真的,而且某些产品(例如 SQL 服务器)非常擅长它。然而,这些产品是罕见的例外。要做到正确,需要大量的纪律、执行和理解。如此之多,以至于人们甚至很少尝试做对。 @JaredPar,所以你基本上是在说因为大多数人没有正确处理错误,你甚至不应该关心错误,而是让应用程序崩溃和烧毁,可能会破坏用户的数据?问题是 OOM 在用户机器上运行时发生。您无法控制这些机器中的内存大小以及交换文件的 HD 空间。然后给它添加内存泄漏......另外,很容易测试你的应用程序可以处理它。使用随机返回 NULL 的 malloc/realloc 包装器。 @Secure,我的意思是尽可能快地失败是获得可操作错误报告的绝对最佳方式。我在我的位置上处理了很多 Watson 错误。快速失败的代码路径会产生非常可操作的数据,并且通常会导致错误被修复。尝试处理 OOM 等情况的代码路径几乎总是 1) 错误地执行或 2) 将其传递给无法处理这种情况的代码。崩溃并产生非常不可操作的错误,因为崩溃发生在最初的真正问题之后很长时间。

以上是关于由于内存导致重新分配失败时如何处理?的主要内容,如果未能解决你的问题,请参考以下文章

前端如何处理内存泄漏

使用 WCF 和 DefaultCredentials 时如何处理密码更改?

[当PHP代码失败但发送200时如何处理AJAX?

GC时如何处理对象引用

UILocalNotification - 应用程序未运行时如何处理?

使用 Kafka Streams DSL 时如何处理错误和不提交