大家都知道:
realloc
重新分配一个已经存在的内存块或者将它复制到更大的内存块。calloc
确保内存被清零,避免算术溢出,并且通常用于大型数组。
C标准为什么不提供一个像下面这样同时结合了这两者的函数呢?
void *recalloc(void *ptr, size_t num, size_t size);
如果能够调整巨大哈希表或自定义内存池的大小,这岂不是非常有用?
大家都知道:
realloc
重新分配一个已经存在的内存块或者将它复制到更大的内存块。calloc
确保内存被清零,避免算术溢出,并且通常用于大型数组。C标准为什么不提供一个像下面这样同时结合了这两者的函数呢?
void *recalloc(void *ptr, size_t num, size_t size);
如果能够调整巨大哈希表或自定义内存池的大小,这岂不是非常有用?
通常在C中,标准库的目的不是提供一组丰富而酷炫的函数,而是提供一个基本的构建块集,您可以使用这些构建块构建自己的酷炫函数。
您对recalloc
的建议很容易编写,因此不应该由标准库提供。
其他语言采用了不同的方法:C#和Java具有超级丰富的库,即使是复杂的任务也变得轻松。但它们带来了巨大的开销。 C的开销很小,这有助于使其适用于各种嵌入式设备。
malloc
页面池的地方(如果可能)获取calloc
页面,并且calloc
使用懒惰分配和写时复制技术从全零页中复制。这就是为什么在Linux上使用calloc
比使用malloc
更快(而且比使用malloc
后再用memset
更快)。 - M.Msize_t
以及许多其他人都使用的东西也没有。 - Mattrecalloc
版本很容易编写,即使使用memset
:为了使recalloc
作为零扩展副本工作,您必须跟踪内存的初始大小。在我看来,realloc
背后的理由是为了减轻用户对分配内存大小的跟踪。如果标准库只提供基本函数,它最初就不会引入realloc
,因为可以很容易地用条件malloc-copy-free语句表达它。 - aprelev我想您只是想将数组的新部分清零:
并不是每个内存分配器都知道您在数组中使用了多少内存。例如,如果我执行以下操作:
char* foo = malloc(1);
foo
现在指向至少1个字节大小的一块内存。但大多数分配器将分配比1个字节更多的内存(例如8个字节,以保持对齐)。
其他分配也可能发生这种情况。内存分配器将分配至少您请求的内存大小,但通常会多分配一点。
而正是这个“多分配一点”的部分使事情变得复杂(除了其他使此难以处理的因素)。因为我们不知道它是否是有用的内存。如果它只是填充,而您使用recalloc
函数重新分配它,并且分配器没有将其清零,则现在您就有了具有某些非零值的“新”内存。
例如,如果我使用recalloc
将foo
重定向到至少2个字节的新缓冲区,那么额外的一个字节会被清零吗?还是不会?应该清零,但请注意,原始分配给我们8个字节,因此重新分配不会分配任何新内存。就分配器而言,它不需要将任何内存清零(因为没有“新”内存需要清零)。这可能会导致我们代码中的严重错误。
calloc
或其自身分配的内存正常工作。 - Mattrecalloc
函数的实用性正在降低。你可以通过添加越来越多的任意限制来使其工作,但在某个时候(很快就会到来),要求用户跟踪内存并在realloc
后清零新内存只是更容易的选择。 - Cornstalksrecalloc
将强制实现清除这些额外的字节,当然。就像某些架构上的对齐要求迫使实现首先分配这些额外的字节一样。 - aprelev
calloc()
有一个malloc()
所没有的特性:在像DOS这样的古老系统中,能够分配比SIZE_MAX
大的数组。因此,即使size_t
是16位的,代码也可以使用calloc(60000u, sizeof(double))
。我一直在想这是否符合C标准,但看起来是正确的。 - chux - Reinstate Monica