如果做下一个:
int* array = malloc(10 * sizeof(int));
他们使用realloc:
array = realloc(array, 5 * sizeof(int));
在第二行(并且只有它),它可以返回NULL
吗?
答案 0 :(得分:11)
是的,它可以。 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&lt; = 6,它分配32个字节,而对于7-10,它是48个。
因此,如果它将int[10]
缩减为int[5]
,则分配的大小将从48缩减到32,实际上有16个空闲字节。因为(正如它已经注意到的)它不会分配少于32个字节的东西,这16个字节就会丢失。
如果它将块移动到其他位置,则将释放整个48个字节,并且实际上可以放入其中的某些内容。当然,这只是一个科幻故事,而不是一个真正的实现;)。
C99标准中最相关的引用( 7.20.3.4 realloc
函数):
<强>返回强>
4
realloc
函数返回指向新对象的指针(可能与指向旧对象的指针具有相同的值),如果新对象可以,则返回空指针不被分配。
'May'是这里的关键词。它没有提到可能发生的任何特定情况,所以你不能依赖它们中的任何一个,即使它们乍一看听起来很明显。
顺便说一下,我认为你可以认为realloc()
有点弃用了。如果您看一下C ++,那么较新的内存分配接口(new
/ delete
和分配器)甚至不支持这样的事情。他们总是希望你分配一个新的块。但这只是一个松散的评论。
答案 1 :(得分:5)
其他答案已经解决了问题,但假设您知道realloc
调用是“修剪”,您可以用以下内容进行包装:
void *safe_trim(void *p, size_t n) {
void *p2 = realloc(p, n);
return p2 ? p2 : p;
}
并且返回值将始终指向大小为n
的对象。
在任何情况下,由于realloc
的实现知道对象的大小,因此可以确定它是“修剪”,从实现的质量角度来看,如果不执行上述操作,那将是病态上的不良逻辑内部。但由于realloc
不需要执行此操作,因此您应该自己执行此操作,使用上述包装器或在调用realloc
时使用类似的内联逻辑。
答案 2 :(得分:3)
语言(和库)规范没有这样的保证,就像它不能保证“修剪”realloc
将保留指针值。
实现可能决定以最“原始”的方式实现realloc
:通过为新内存块执行无条件malloc
,复制数据和free
- 旧的块。显然,这种实现可能会在内存不足的情况下失败。
答案 3 :(得分:3)
不要指望它。该标准没有规定;如果无法分配新对象,它只会声明“或空指针”。
你很难找到这样的实现,但根据标准,它仍然是合规的。
答案 4 :(得分:2)
我怀疑在您描述的情景中可能存在理论失败的可能性。
根据堆实现,可能没有修剪现有分配块的事情。而是首先分配一个较小的块,然后从旧的块中复制数据,然后释放它。
例如,这可能是桶堆策略的情况(由一些流行的堆使用,例如tcmalloc)。
答案 5 :(得分:1)
有点晚了,但至少有一个流行的实现,realloc()
具有超大尺寸可能会失败:TCMalloc。 (至少就我理解的代码而言)
如果您在函数tcmalloc.cc
中读取文件do_realloc_with_callback()
,您将看到如果缩小到足够的(50%的已分配内存,否则将被忽略),TCMalloc将分配新的内存首先(并可能失败),然后复制它并删除旧内存。
我不复制源代码,因为我不确定(TCMalloc和Stackoverflow)的版权是否允许,但这里是link to the source(修订截至2019年5月17日)。
答案 6 :(得分:-1)
realloc
在缩小现有内存时不会失败,因此不会返回NULL
。只有在扩展期间失败时,它才能返回NULL
。
但是在一些架构中缩小可能会失败,其中realloc
可以以不同的方式实现,例如分别分配较小的内存并释放旧内存以避免碎片。在这种情况下,收缩内存可以返回NULL。但它的实施非常罕见。
但更好的做法是在更安全的一面,在缩小内存后保持NULL
检查。