【发布时间】:2012-08-20 22:57:59
【问题描述】:
如果执行下一步:
int* array = malloc(10 * sizeof(int));
我使用 realloc:
array = realloc(array, 5 * sizeof(int));
在第二行(只有它),它可以返回NULL吗?
【问题讨论】:
标签: c memory-management realloc
如果执行下一步:
int* array = malloc(10 * sizeof(int));
我使用 realloc:
array = realloc(array, 5 * sizeof(int));
在第二行(只有它),它可以返回NULL吗?
【问题讨论】:
标签: c memory-management realloc
是的,它可以。realloc() 没有实现保证,即使在收缩时它也可以返回不同的指针。
例如,如果一个特定的实现为不同的对象大小使用不同的池,realloc() 实际上可能会在池中为较小的对象分配一个新块,并为较大的对象释放池中的块。因此,如果较小对象的池已满,它将失败并返回 NULL。
或者它可能只是决定移动块更好
我刚刚使用以下程序通过 glibc 获取实际分配的内存大小:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int n;
for (n = 0; n <= 10; ++n)
{
void* array = malloc(n * sizeof(int));
size_t* a2 = (size_t*) array;
printf("%d -> %zu\n", n, a2[-1]);
}
}
n
因此,如果将 int[10] 缩小到 int[5],分配的大小将从 48 缩小到 32,有效地提供 16 个空闲字节。因为(正如刚刚提到的)它不会分配小于 32 字节的任何内容,所以这 16 字节会丢失。
如果它将块移动到其他地方,整个 48 个字节将被释放,并且实际上可以在其中放入一些东西。当然,这只是一个科幻故事,并不是真正的实现;)。
C99 标准中最相关的引用(7.20.3.4 realloc 函数):
退货
4
realloc函数返回一个指向新对象的指针(可能与旧对象的指针具有相同的值),或者如果新对象不能是空指针已分配。
'May' 是这里的关键词。它没有提及可能发生这种情况的任何具体情况,因此您不能依赖其中任何一种,即使它们乍一看听起来很明显。
顺便说一句,我认为您可以认为 realloc() 有点过时了。如果你看一下 C++,新的内存分配接口(@987654331@/delete 和分配器)甚至不支持这样的事情。他们总是希望你分配一个新块。但这只是一个松散的评论。
【讨论】:
realloc 已弃用,因为 C++ 在新/删除机制中没有类似物。 C++ 是一种与 C 非常不同的语言,特别是,在 C++ 中支持移动对象需要实现某种方式来通知对象它正在被重定位并允许它更新自己的内部引用。另一方面,C 不会自动化或封装任何这些,因此由调用者(因此非常好)负责在 realloc 之后是否需要更改对象的内容。
其他答案已经解决了这个问题,但假设您知道 realloc 调用是“修剪”,您可以将其包装为:
void *safe_trim(void *p, size_t n) {
void *p2 = realloc(p, n);
return p2 ? p2 : p;
}
并且返回值将始终指向大小为n的对象。
无论如何,由于realloc 的实现知道对象的大小,因此可以确定它正在“修剪”,从实现质量的角度来看,如果不在内部执行上述逻辑,那将是病态的糟糕.但是由于不需要realloc 来执行此操作,因此您应该自己执行此操作,可以使用上述包装器,也可以在调用realloc 时使用类似的内联逻辑。
【讨论】:
malloc 的下一次调用将在其他地方失败,这将(至少在一个健壮的程序中)处于程序可以处理失败情况、退出任何部分工作并报告错误的地步.
realloc 在健壮的程序中将毫无用处。这实际上是一种极其常见的内存泄漏形式(即p=realloc(p,newsize);,如果realloc 失败,则会丢失旧内存)。
语言(和库)规范没有做出这样的保证,就像它不保证“修剪”realloc 将保留指针值一样。
实现可能决定以最“原始”的方式实现realloc:通过对新内存块执行无条件的malloc、复制数据和free-ing 旧块。显然,这种实现在内存不足的情况下可能会失败。
【讨论】:
不要指望它。标准没有这样的规定;它只是声明“如果无法分配新对象,则为空指针”。
你很难找到这样的实现,但根据标准,它仍然是合规的。
【讨论】:
我怀疑在您描述的场景中可能存在理论上失败的可能性。
根据堆实现的不同,可能没有修剪现有分配块这样的事情。相反,首先分配一个较小的块,然后从旧块中复制数据,然后将其释放。
例如,桶堆策略可能就是这种情况(一些流行的堆使用,例如 tcmalloc)。
【讨论】:
realloc 永远不会失败的错误代码。
tcmalloc.cc 函数do_realloc() 用于tc_realloc(), (github.com/gperftools/gperftools/blob/master/src/…)
有点晚了,但至少有一个流行的实现,realloc() 具有较小的大小可能会失败:TCMalloc。 (至少就我理解的代码而言)
如果你读取文件tcmalloc.cc,在函数do_realloc_with_callback()中,你会看到如果你收缩得足够多(分配的内存的50%,否则会被忽略),TCMalloc会先分配新的内存(并且可能失败)然后复制它并删除旧内存。
我不会复制源代码,因为我不确定(TCMalloc 和 Stackoverflow 的)版权是否允许这样做,但这里有一个 link to the source(修订于 2019 年 5 月 17 日)。
【讨论】:
realloc 不会在收缩现有内存时失败,因此它不会返回NULL。只有在扩展过程中失败,它才能返回NULL。
但是在某些架构中,收缩可能会失败,realloc 可以以不同的方式实现,例如单独分配较小的内存并释放旧内存以避免碎片。在这种情况下,缩小内存可以返回 NULL。但它的实现非常罕见。
但最好是在更安全的一面,在缩小内存后保持NULL检查。
【讨论】: