realloc 失败的可能性有多大?

Posted

技术标签:

【中文标题】realloc 失败的可能性有多大?【英文标题】:What are the chances that realloc should fail? 【发布时间】:2011-06-22 23:19:34 【问题描述】:

类似malloc的空闲内存用完时会失败还是有其他原因?

【问题讨论】:

【参考方案1】:

任何分配函数(mallocrealloccalloc 和 POSIX 上的posix_memalign)都可能由于以下任何原因失败,也可能是其他原因:

您已经用完了整个虚拟地址空间,或者至少是其中的可用部分。在 32 位机器上,只有 4GB 的地址,可能有 1GB 左右是为操作系统内核保留的。即使你的机器有 16GB 的物理内存,单个进程也不能使用超过它的地址。 您的虚拟地址空间并没有用完,但是您已经将其严重分割,以至于无法使用所请求大小的连续地址范围。如果您成功分配 6 512MB 块,每隔一个释放一个块,然后尝试分配 1GB 块,则可能会发生这种情况(在 32 位机器上)。当然还有很多其他内存较小的例子。 您的计算机已用完物理内存,原因可能是您自己的程序已全部使用,或者计算机上运行的其他程序已全部使用。某些系统(默认配置中的 Linux)会overcommit,这意味着malloc 在这种情况下不会失败,但是操作系统会在以后发现还不够时杀死一个或多个程序物理内存来回走动。但在健壮的系统上(包括禁用过度使用的 Linux),如果没有剩余物理内存,malloc 将失败。

请注意,严格来说,分配函数可以在任何时候因任何原因失败。最小化失败是一个实施质量问题。 realloc 也有可能失败,即使 减小 对象的大小;这可能发生在按大小严格隔离分配的实现上。当然,在这种情况下,您可以简单地继续使用旧的(更大的)对象。

【讨论】:

【参考方案2】:

您应该认为realloc 是这样工作的:

void *realloc(void *oldptr, size_t newsize)

   size_t oldsize = __extract_size_of_malloc_block(oldptr);
   void *newptr = malloc(newsize);

   if (!newptr)
     return 0;

   if (oldsize > newsize)
     oldsize = newsize;

   memcpy(newptr, oldptr, oldsize);
   free(oldptr);
   return newptr;

一个实现可能能够比这更有效地处理特定情况,但是一个完全如图所示工作的实现是 100% 正确的。这意味着realloc(ptr, newsize) 随时可能失败,malloc(newsize) 会失败;特别是它可能会失败即使你正在缩小分配

现在,在现代桌面系统上,有充分的理由不尝试从malloc 故障中恢复,而是将malloc 包装在一个函数(通常称为xmalloc)中,如果malloc 会立即终止程序失败;自然,同样的论点也适用于realloc。案例是:

    桌面系统经常运行在“overcommit”模式下,假设程序实际上不会使用所有的地址空间,内核会很乐意分配比 RAM+swap 支持的更多的地址空间。如果程序确实尝试使用所有这些,它将被强制终止。在此类系统上,malloc 只会在您耗尽 地址空间 时才会失败,这在 32 位系统上不太可能,而在 64 位系统上几乎不可能。 即使您没有处于过度使用模式,桌面系统也很有可能拥有如此多的 RAM 和可用交换空间,以至于在您导致 malloc 失败之前很久,用户就会厌倦他们的抖动磁盘和强行终止您的程序。 没有实用的方法测试从分配失败中恢复;即使您有一个 shim 库可以准确控制对 malloc 的哪些调用失败(根据操作系统,这样的 shim 最多很难创建,最坏的情况是不可能创建)您必须测试 2N 的顺序 失败模式,其中 N 是程序中对 malloc 的调用次数。

参数 1 和 2 不适用于嵌入式或移动系统(还没有!),但参数 3 在那里仍然有效。

参数 3 仅适用于必须在每个调用站点检查和传播分配失败的程序。如果您幸运地使用了 C++ 的预期用途(即有例外情况),您可以依靠编译器为您创建错误恢复路径,从而大大减轻测试负担。而且在当今任何值得使用的高级语言中,您都有异常和垃圾收集器,这意味着即使您愿意也不必担心分配失败。

【讨论】:

【参考方案3】:

我会说它主要是特定于实现的。一些实现很可能会失败。在 realloc 之前,有些程序的其他部分可能会失败。始终保持防御并检查它是否失败。

记得释放你试图重新分配的 old 指针。

ptr=realloc(ptr,10);

总是可能的内存泄漏。

总是这样做:

void *tmp=ptr;
if(ptr=realloc(ptr,10)==NULL)
  free(tmp);
  //handle error...

【讨论】:

我相信该标准对此的唯一说法是“[i]如果无法分配空间”返回一个空指针,没有更具体的原因。 在无法为新数据腾出空间时丢弃旧数据可能不是大多数应用程序的正确行为... @R.. 是的。我只是警告常见的内存泄漏 缺少括号:if(ptr=realloc(ptr,10)==NULL) -> if ((ptr = realloc(ptr, 10)) == NULL) 【参考方案4】:

你有两个问题。

mallocrealloc 失败的可能性在大多数现代系统上可以忽略不计。这仅在您用完虚拟内存时发生。您的系统将无法访问内存而不是保留它。

W.r.t 失败 reallocmalloc 几乎相等。 realloc 可能失败的唯一原因是你给它一个错误的参数,即没有分配给 mallocrealloc 或以前是 freed 的内存。

编辑:鉴于 R. 的评论。是的,您可以配置您的系统,使其在分配时失败。但首先,AFAIK,这不是默认设置。它需要以这种方式配置权限,作为应用程序程序员,这不是您可以指望的。其次,即使您有一个以这种方式配置的系统,这也只会在您的可用交换空间被耗尽时出错。通常,您的机器在此之前很久就无法使用:它将在您的硬盘上进行机械计算(AKA 交换)。

【讨论】:

访问分配失败不是“现代”行为。这是一种懒惰的行为。提交会计很难,在 Linux 开发的早期,每个人都懒得做对。几十年来,健壮的 unix 系统已经进行了正确的提交记账,现在 Linux 也可以配置为正确记账。 我认为应用程序开发人员没有责任担心启用过量使用的可能性。对于您的程序在访问已经“成功”分配的内存时可能会崩溃的可能性,没有好的解决方法。你可以诱捕SIGSEGV,但如果你抓住它怎么办?我想您可以从顶部的虚拟文件重新映射MAP_SHARED 页面,然后从信号处理程序返回,并让调用者检测到这种情况发生... @R.:分配后,您可以暂时捕获SIGSEGVSIGBUS 并循环访问页面。这样你至少可以限制错误,然后优雅地失败。分配的初始开销会很明显,但我认为,如果真正使用所有内存,摊销成本是可以容忍的。 我认为你必须像我描述的那样做一些事情,因为否则即使你捕捉到信号,也没有办法从信号处理程序返回。您必须进行更改,以使其在返回后不会再次出现故障... 我在 Linux 的早期开发过程中并没有关注它,但我 系统管理了一堆 SunOS 4 和 Solaris 2.x (x 大肆宣传为一个特性——你巨大的静态 Fortran 数组,其中只有一小部分实际用于程序的典型运行,不会带来计算机因寻呼而崩溃! (如果您没有配置足够的 RAM 和/或交换来解决您的问题,Sun Microsystems 不承担任何责任。)【参考方案5】:

来自zwol的answer:

现在,在现代桌面系统上,有充分的理由不尝试从malloc 故障中恢复,而是将malloc 包装在一个函数(通常称为xmalloc)中,如果malloc 会立即终止程序失败; 当然,同样的论点也适用于realloc

您可以看到该原则适用于 Git 2.29(2020 年第四季度):xrealloc() 可以发送已被释放的非 NULL 指针,该指针已被修复。

参见 Jeff King (peff) 的 commit 6479ea4(2020 年 9 月 2 日)。(由 Junio C Hamano -- gitster -- 合并到 commit 56b891e,2020 年 9 月 3 日)

xrealloc:不要重用由零长度 realloc() 释放的指针

签字人:杰夫·金

此补丁修复了xrealloc(ptr, 0) 在某些平台(至少包括glibc)上可以双重释放和损坏堆的错误。

C99 标准提到 malloc(第 7.20.3 节):

If the size of the space requested is zero, the behavior is
implementation-defined: either a null pointer is returned, or the
behavior is as if the size were some nonzero value, except that the
returned pointer shall not be used to access an object.  

所以我们可能会得到NULL,或者我们可能会得到一个实际的指针(但我们不能查看它的内容)。

为了简化我们的代码,我们的 xmalloc() 通过将 NULL 返回转换为单字节分配来处理它。 这样来电者就会得到一致的行为。这是在 4e7a2eccc2 中完成的(“?alloc:当要求零字节时不要返回 NULL”,2005-12-29,Git v1.1.0 -- merge)。

我们还对xcalloc()xrealloc() 进行了同样的处理。根据 C99,这很好;上面的文字在适用于所有三个的段落中。

但是在这种情况下,我们传递给realloc() 的内存会发生什么?即,如果我们这样做:

ret = realloc(ptr, 0);  

并且“ptr”是非NULL,但我们得到NULL:“ptr”仍然有效吗? C99 没有具体涵盖这种情况,但表示(第 7.20.3.4 节):

The realloc function deallocates the old object pointed to by ptr and
returns a pointer to a new object that has the size specified by size.  

所以“ptr”现在已被释放,我们只能查看“ret”。 由于“ret”是NULL,这意味着我们根本没有分配对象。但这还不是全部。它还说:

If memory for the new object cannot be allocated, the old object is
not deallocated and its value is unchanged.
[...]
The realloc function returns a pointer to the new object (which may
have the same value as a pointer to the old object), or a null pointer
if the new object could not be allocated.  

因此,如果我们看到 NULL 返回的大小非零,我们可以预期原始对象仍然有效。 但是对于非零大小,它是模棱两可的。 NULL 返回可能意味着失败(在这种情况下对象有效),或者可能意味着我们没有成功分配任何内容,并使用 NULL 来表示。

realloc()glibc 手册页明确表示:

[...]if size is equal to zero, and ptr is not NULL, then the call is
equivalent to free(ptr).  

同样,这个 *** 对“What does malloc(0) return?”的回答: 声称 C89 给出了类似的指导(但我没有副本来验证它)。

评论 on this answer to "What's the point of malloc(0)?" 声称微软的 CRT 行为相同。

但是我们当前的“使用 1 字节重试”代码再次传递了原始指针。 所以在glibc 上,我们有效地free() 指针然后尝试再次realloc() 它,这是未定义的行为。

这里最简单的解决方法是将“ret”(我们知道是NULL)传递给后续realloc()。 但这意味着释放原始指针的系统会泄漏它。目前尚不清楚是否存在任何此类系统,并且对标准的解释似乎不太可能(我希望在这种情况下不会释放的系统只是简单地返回原始指针)。 但是为了安全起见,很容易犯错,而且永远不要将零大小传递给realloc()

【讨论】:

以上是关于realloc 失败的可能性有多大?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以告诉分支预测器跟随分支的可能性有多大?

在继续之前需要等待循环完成执行的可能性有多大

PayPal 集成 (IPN) - 真的有多大可能?

两条消息具有相同 MD5 摘要和相同 SHA1 摘要的可能性有多大?

Android中的Binder事务失败[重复]

整数缓存有多大?