calloc比malloc更好吗?

19

我最近才学习了C语言中的calloc()函数。阅读了它的描述以及与malloc的区别 (1, 2)后,我认为,作为一名非嵌入式程序员,我应该总是使用calloc()。但这是否真的是这样呢?

我有一个保留意见,就是访问calloc()分配的内存会有额外的延迟,但我也想知道是否有一些情况,在这些情况下从malloc()切换到calloc()会以一些更严重的方式破坏程序。

P.S. calloc()中零初始化方面的内容对我来说很清楚。我感兴趣的是calloc()与malloc()之间的另一个区别——由calloc()提供的惰性内存分配。如果你只关注内存初始化方面,请不要发表回答。


4
为什么在 C++ 中你想要使用 calloc() 函数? - Sourav Ghosh
4
C还是C++?在C++中,手动内存分配,特别是使用*alloc函数被视为不好的做法。 - NathanOliver
1
@VioletGiraffe 如果你需要一个固定大小的缓冲区,我会选择使用 std::unique_ptr<T[]>。这样你就有了内置的内存管理。 - NathanOliver
1
@VioletGiraffe 有什么运行时间开销?如果你有手动分配的指针,最终还是需要删除它。std::unique_ptr 仅将该删除绑定到指针范围,因此您无需记住它。具有默认删除器的 std::unique_ptr 应与自己完成同样有效,而且不会出现错误的好处。 - NathanOliver
1
@VioletGiraffe,它基本上就像你所说的那样。它没有线程安全性,没有原子引用计数,也没有空检查。它旨在尽可能轻量化。它确实将删除器存储在结构中,但由于空基类优化,如果它只是一个空删除器,比如调用delete[],那么它甚至不会使指针的大小变大。在gcc中,sizeof(std::unique_ptr<int[]>)8,与单个指针(64位)的大小相同。 - NathanOliver
显示剩余13条评论
5个回答

13

这是一个非常情况依赖的决定。经验法则是:

  • 如果您是第一次写入分配的内存,那么使用malloc()更好(可能会有较少的开销)。

    例如:考虑以下情况

char * pointer = NULL;

//allocation

strcpy(pointer, source);

在这里,可以使用malloc()非常好地进行内存分配。

  • 如果在分配的内存中存在读写顺序错误的可能性,请使用calloc(),因为它会初始化内存。这样,您就可以避免未初始化内存读写顺序错误的问题,从而避免调用未定义行为

    例如:

    char * pointer = NULL;
    
    //allocation
    
    strcat(pointer, source);
    

    在这里,strcat()需要第一个参数已经是一个字符串,并且使用malloc()进行分配不能保证这一点。由于calloc()会对内存进行零初始化,因此它将在这里起到作用,因此calloc()是应该选择的方法。

    为了阐述第二种情况,引用自C11,第§7.24.3.1章节(我的重点

    strcat()函数将s2指向的字符串(包括终止空字符)的副本附加到s1指向的字符串的末尾s2的初始字符覆盖s1末尾的空字符。[....]

    因此,在这种情况下,目标指针应该是指向字符串的指针。通过calloc()进行分配可以保证这一点,而使用malloc()进行分配不能保证,正如我们所知道的,来自第§7.22.3.4章节

      

    malloc函数分配大小由size指定并且值不确定的对象的空间。


    编辑:

    建议在编写用于单元/集成测试的测试桩时使用malloc()而不是calloc()的一个可能情况是,calloc()的使用可以隐藏与后者类似的潜在错误。


  • 零初始化方面是非常明确的。但是 calloc()提供的惰性内存分配又是怎样的呢? - Violet Giraffe
    @VioletGiraffe 就我所知,这是内存分配器的属性,在特定平台上对于malloc()和/或calloc()同样适用。你能详细说明一下吗? - Sourav Ghosh
    链接在我的问题中:https://dev59.com/7XE85IYBdhLWcg3wnU6p#2688522 和 https://vorpus.org/blog/why-does-calloc-exist/。 - Violet Giraffe
    1
    如果你需要将char数组的第一个字符设置为'\0',那就直接这么做。对于这样简单的初始化,使用calloc会过度杀伤。 - Pete Becker
    @PeteBecker 那只是一个示例场景,我们肯定可以找到更复杂的场景,需要整个内存被初始化。 :) - Sourav Ghosh

    3
    malloccalloc 的主要区别在于 calloc 会将缓冲区初始化为零,而 malloc 则不会对内存进行初始化。
    这涉及到常见的编程习语“不要为你不使用的东西付费”。换句话说,如果你还不需要零初始化某些内容,为什么要进行零初始化(这是有成本的)?
    顺便提一下,因为你标记了 C++:在现代 C++ 中,手动使用 new/delete 来分配内存是不被推荐的(除非是在内存池等少数情况下)。使用 malloc/free 更加罕见,应该非常谨慎地使用。

    2

    对于需要清零的分配,使用calloc,但仅当真正需要清零时才使用。

    您应始终使用calloc(count,size)而不是buff=malloc(total_size); memset(buff,0,total_size)

    memset的调用是关键。 malloccalloc都被转换为执行许多优化、尽可能使用硬件技巧等操作系统调用。 但是,操作系统在memset方面可以做的很少。

    另一方面,何时需要填充分配的内存?唯一常见的用途是用于以零结尾的任意长度元素,例如C字符串。 如果是这种情况,请使用calloc

    但是,如果您分配的结构中的元素具有固定长度或携带带有它们的任意大小元素的长度(例如C ++字符串和向量),则填充零不起作用,如果您尝试依赖它,则可能会导致棘手的错误。

    假设您编写了自定义链接列表,并决定跳过指向下一个节点的指针的清零,方法是使用calloc为节点分配内存。 它起作用,然后有人使用自定义放置新对象,它不会填充零。问题在于,有时它将被填充为零,并且可以通过所有通常的测试,进入生产环境,然后会崩溃,有时会崩溃,这是可怕的无法重复的错误。

    出于调试目的,填充零通常不是那么好的选择。 0太常见了,您很少能写出像assert(size);这样的东西,因为它通常也是有效值,您需要使用if(!size)而不是asserts处理它。 在调试器中,它也不会引起注意,您的内存中通常到处都是零。 最佳实践是避免用于长度的无符号类型(对于运行时错误处理和一些常见的溢出检查,有符号长度也可能很有用)。 因此,应避免使用buff=malloc(total_size); memset(buff,0,total_size),而以下操作则没有问题:

    const signed char UNINIT_MEM=MY_SENTINEL_VALUE;
    buff=malloc(total_size);
    #if DEBUG_MEMORY
    memset(buff,UNINIT_MEM,total_size);
    #endif
    

    在调试模式下,运行时库甚至操作系统有时会为您执行此操作,例如检查VC++特定哨兵值的这篇优秀文章


    1

    这完全取决于您想用内存做什么。malloc 返回未初始化的(可能甚至还不存在的)内存。calloc 返回真实的、零值的内存。如果您需要它被清零,那么是的,calloc 是您最好的选择。如果不需要,为什么要在不需要时付出延迟成本来清零呢?


    我更感兴趣的是 calloc() 提供的惰性分配。 - Violet Giraffe
    1
    @VioletGiraffe,那你肯定想要使用malloc。在Linux上,malloc返回的指针甚至不指向实际内存。只有当程序第一次访问该内存时,内存才会变得真实。 - Paul Evans
    @VioletGiraffe 目前为止,calloc() 在任何常见平台上都不会进行惰性分配。但是,malloc() 会进行惰性分配。 - nos
    @nos:我在我的问题中提供了两篇文章的链接(一篇文章和一个SO答案),它们都表明相反的情况。它们是错误的吗? - Violet Giraffe

    1

    在C代码中,malloc()比calloc()更为常见。

    搜索"malloc"文本会忽略掉对calloc()的调用。

    替换库通常会有mymalloc()、myrealloc()和myfree(),但没有mycalloc()。

    指针和实数的零初始化并不能保证有预期的效果,尽管在每个主要平台上,所有位都为零对于指针而言是NULL,对于实数而言是0.0。

    calloc()往往会隐藏错误。调试malloc通常会设置一个填充模式,如DEADBEEF,它被计算为一个很大的负数,看起来不像真实数据。因此程序很快就会崩溃,并且使用调试器可以找出错误。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接