如果按照以下步骤进行:
int* array = malloc(10 * sizeof(int));
然后我使用realloc:
array = realloc(array, 5 * sizeof(int));
在第二行(且仅在第二行),它能返回NULL
吗?
如果按照以下步骤进行:
int* array = malloc(10 * sizeof(int));
然后我使用realloc:
array = realloc(array, 5 * sizeof(int));
在第二行(且仅在第二行),它能返回NULL
吗?
可以的。 关于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 <= 6,它分配32字节,对于7-10,则为48字节。realloc
称为弃用函数,只是因为 C++ 在 new/delete 中没有类似的函数。C++ 是一种与 C 非常不同的语言,特别是支持移动对象需要一些方法来通知对象正在被重新定位,并允许其更新自己的内部引用。另一方面,C 并没有自动化或封装任何这些操作,因此由调用者(因此完全可以)负责确定在 realloc
后对象的内容是否需要更改。 - R.. GitHub STOP HELPING ICErealloc
调用是“删减内存”的话,您可以使用以下方法包装它:void *safe_trim(void *p, size_t n) {
void *p2 = realloc(p, n);
return p2 ? p2 : p;
}
返回值始终指向大小为n
的对象。
无论如何,由于realloc
的实现知道对象的大小并且因此可以确定它正在“修剪”,从实现质量的角度来看,不执行上述逻辑在病态情况下是非常糟糕的。但是,由于realloc
不需要这样做,您应该自己执行此操作,使用上面的包装器或类似的内联逻辑调用realloc
时执行。
malloc
的调用会在其他地方失败,在一个健壮的程序中,也将在程序可以处理失败情况、撤销任何部分工作并报告错误的点上进行。 - R.. GitHub STOP HELPING ICE语言规范(和库)不做此类保证,就像它不保证“trimming”realloc
将保留指针值一样。
实现可能决定以最“原始”的方式实现realloc
:通过对新的内存块进行无条件malloc
,复制数据并释放旧块。显然,这种实现在低内存情况下可能会失败。
我怀疑在你描述的情况下存在一个理论上的可能失败的情况。
根据堆实现的不同,可能不存在修剪现有分配块的情况。相反,首先分配一个较小的块,然后将数据从旧块复制过去,最后再释放它。
例如,这可能是桶堆策略的情况(一些流行的堆,如tcmalloc使用此策略)。
realloc
永远不会失败的糟糕代码。 - R.. GitHub STOP HELPING ICEtcmalloc.cc
中的函数do_realloc()
,该函数用于tc_realloc()
。(https://github.com/gperftools/gperftools/blob/master/src/tcmalloc.cc#L1446) - 12431234123412341234123realloc()
可能会失败:TCMalloc。(至少就我理解代码而言)tcmalloc.cc
,在函数do_realloc_with_callback()
中,您将看到如果收缩足够(分配内存的50%,否则将被忽略),TCMalloc将首先分配新内存(并可能失败),然后复制它并删除旧内存。realloc
在缩小现有内存时不会失败,因此它不会返回NULL
。只有在扩展期间失败时才会返回NULL
。
但在某些架构中,收缩可能会失败,其中realloc
可以以不同的方式实现,例如分配较小的大小内存并释放旧内存以避免碎片化。在这种情况下,收缩内存可能会返回NULL。但这是非常罕见的实现。
但为了更安全起见,在缩小内存后也最好进行NULL
检查。