【问题标题】:Difference between malloc and calloc?malloc 和 calloc 的区别?
【发布时间】:2010-12-05 00:52:30
【问题描述】:

做有什么区别:

ptr = malloc (MAXELEMS * sizeof(char *));

或:

ptr = calloc (MAXELEMS, sizeof(char*));

什么时候最好使用 calloc 而不是 malloc,反之亦然?

【问题讨论】:

标签: c malloc calloc


【解决方案1】:

有两个区别。
首先,是论据的数量。 malloc() 需要一个参数(需要的内存以字节为单位),而calloc() 需要两个参数。
其次,malloc()不初始化分配的内存,而calloc()将分配的内存初始化为零。

  • calloc() 分配一个内存区域,长度将是其参数的乘积。 calloc 用零填充内存并返回指向第一个字节的指针。如果它未能找到足够的空间,它会返回一个 NULL 指针。

语法:ptr_var = calloc(no_of_blocks, size_of_each_block); ptr_var = calloc(n, s);

  • malloc() 分配 REQUSTED SIZE 的单个内存块并返回指向第一个字节的指针。如果它未能找到请求的内存量,它会返回一个空指针。

语法:ptr_var = malloc(Size_in_bytes); malloc() 函数有一个参数,即要分配的字节数,而calloc() 函数有两个参数,一个是元素数,另一个是要为每个元素分配的字节数.此外,calloc() 将分配的空间初始化为零,而malloc() 不会。

【讨论】:

    【解决方案2】:

    文档使calloc 看起来像malloc,它只是对内存进行零初始化;这不是主要区别! calloc 的想法是为内存分配抽象写时复制语义。当您使用calloc 分配内存时,它都映射到初始化为零的相同物理页面。当分配的内存的任何页面被写入物理页面时被分配。这通常用于制作巨大的哈希表,例如因为空的哈希部分没有任何额外的内存(页面)支持;他们高兴地指向单个零初始化页面,甚至可以在进程之间共享。

    任何对虚拟地址的写入都映射到一个页面,如果该页面是零页面,则分配另一个物理页面,将零页面复制到那里并将控制流返回给客户端进程。这与内存映射文件、虚拟内存等的工作方式相同。它使用分页。

    这是一个关于该主题的优化故事: http://blogs.fau.de/hager/2007/05/08/benchmarking-fun-with-calloc-and-zero-pages/

    【讨论】:

      【解决方案3】:

      块数:
      malloc() 分配单个请求内存块,
      calloc() 分配多个请求内存块

      初始化:
      malloc() - 不清除和初始化分配的内存。
      calloc() - 将分配的内存初始化为零。

      速度:
      malloc() 很快。
      calloc() 比 malloc() 慢。

      参数和语法:
      malloc() 接受 1 个参数:

      1. 字节

        • 要分配的字节数

      calloc() 接受 2 个参数:

      1. 长度

        • 要分配的内存块数
      2. 字节

        • 每个内存块分配的字节数
      void *malloc(size_t bytes);         
      void *calloc(size_t length, size_t bytes);      
      

      内存分配方式:
      malloc 函数从可用堆中分配所需“大小”的内存。
      calloc 函数分配的内存大小等于“num *size”。

      名称含义:
      malloc 这个名字的意思是“内存分配”。
      calloc 这个名字的意思是“连续分配”。

      【讨论】:

        【解决方案4】:

        malloccalloc 都分配内存,但 calloc 将所有位初始化为零,而 malloc 不会。

        Calloc 可以说等价于 malloc + memset 为 0(其中 memset 将指定的内存位设置为零)。

        所以如果不需要初始化为零,那么使用 malloc 可能会更快。

        【讨论】:

          【解决方案5】:

          calloc() 为您提供一个零初始化缓冲区,而malloc() 则保留未初始化的内存。

          对于大型分配,主流操作系统下的大多数calloc 实现将从操作系统(例如通过 POSIX mmap(MAP_ANONYMOUS) 或 Windows VirtualAlloc)获得已知零页面,因此不需要在用户空间中编写它们.这也是malloc 从操作系统获取更多页面的正常方式; calloc 只是利用了操作系统的保证。

          这意味着calloc 内存仍然可以是“干净的”和延迟分配的,并且写入时复制映射到系统范围的共享零物理页。 (假设系统具有虚拟内存。)

          有些编译器甚至可以为你优化 malloc + memset(0) 为 calloc,但如果你想让内存读为0,你应该显式使用 calloc。

          如果您在写入之前不打算读取内存,请使用malloc,这样它就可以(可能)从其内部空闲列表中为您提供脏内存,而不是从操作系统获取新页面。 (或者不是将空闲列表上的一块内存归零以进行少量分配)。


          如果没有操作系统,calloc 的嵌入式实现可能会将其自身保持为零内存,或者它不是一个花哨的多用户操作系统,它会将页面归零以阻止进程之间的信息泄漏。

          在嵌入式 Linux 上,malloc 可以 mmap(MAP_UNINITIALIZED|MAP_ANONYMOUS),这仅对某些嵌入式内核启用,因为它在多用户系统上不安全。

          【讨论】:

          • *alloc 变体非常容易记忆 - clear-alloc、memory-alloc、re-alloc。
          • 如果您要设置在分配空间中使用的所有内容,请使用 malloc()。如果您要保留部分数据未初始化,请使用 calloc() - 将未设置的部分归零将是有益的。
          • calloc 不一定更昂贵,因为操作系统可以采取一些技巧来加速它。我知道 FreeBSD 在获得任何空闲 CPU 时间时,会使用它来运行一个简单的进程,该进程只是四处走动并将已释放的内存块清零,并因此用标志标记块。因此,当您执行calloc 时,它首先会尝试找到一个这样的预置零块并将其提供给您 - 并且很可能会找到一个。
          • 我倾向于认为,如果您的代码由于默认的零初始化分配而变得“更安全”,那么无论您使用 malloc 还是 calloc,您的代码都不够安全。使用 malloc 是数据需要初始化的一个很好的指标——我只在那些 0 字节实际上有意义的情况下使用 calloc。另请注意, calloc 不一定会按照您对非字符类型的想法执行。没有人真正使用陷阱表示或非 IEEE 浮点数,但这不是认为您的代码真正可移植的借口。
          • @SteveJessop “更安全”不是正确的词。我认为“确定性”是更好的术语。更具确定性的代码,而不是依赖于时序和数据序列的故障,将更容易隔离故障。与显式初始化相比,Calloc 有时是一种获得确定性的简单方法。
          【解决方案6】:

          区别1:

          malloc()通常分配内存块,它是初始化的内存段。

          calloc()分配内存块并将所有内存块初始化为0。

          区别2:

          如果你考虑malloc() 语法,它只需要1 个参数。考虑下面的例子:

          data_type ptr = (cast_type *)malloc( sizeof(data_type)*no_of_blocks );
          

          例如:如果你想为 int 类型分配 10 块内存,

          int *ptr = (int *) malloc(sizeof(int) * 10 );
          

          如果您考虑 calloc() 语法,它将采用 2 个参数。考虑下面的例子:

          data_type ptr = (cast_type *)calloc(no_of_blocks, (sizeof(data_type)));
          

          例如:如果您想为 int 类型分配 10 个内存块并将所有这些内存块初始化为零,

          int *ptr = (int *) calloc(10, (sizeof(int)));
          

          相似度:

          malloc()calloc() 如果未进行类型转换,默认情况下都将返回 void*。!

          【讨论】:

          • 你为什么要保持 data_type 和 cast_type 不同?
          【解决方案7】:

          malloc()calloc() 是 C 标准库中允许动态内存分配的函数,这意味着它们都允许在运行时分配内存。

          他们的原型如下:

          void *malloc( size_t n);
          void *calloc( size_t n, size_t t)
          

          两者的区别主要有两点:

          • 行为:malloc() 分配了一个内存块,但没有对其进行初始化,从该块中读取内容将导致垃圾值。而calloc()则分配了一个内存块并将其初始化为零,显然读取此块的内容会导致零。

          • 语法:malloc() 接受 1 个参数(要分配的大小),calloc() 接受两个参数(要分配的块数和每个块的大小)。

          如果成功,两者的返回值都是指向已分配内存块的指针。否则返回NULL,表示内存分配失败。

          示例:

          int *arr;
          
          // allocate memory for 10 integers with garbage values
          arr = (int *)malloc(10 * sizeof(int)); 
          
          // allocate memory for 10 integers and sets all of them to 0
          arr = (int *)calloc(10, sizeof(int));
          

          使用malloc()memset() 可以实现与calloc() 相同的功能:

          // allocate memory for 10 integers with garbage values   
          arr= (int *)malloc(10 * sizeof(int));
          // set all of them to 0
          memset(arr, 0, 10 * sizeof(int)); 
          

          请注意,malloc()calloc() 更适合使用,因为它更快。如果需要对值进行零初始化,请改用calloc()

          【讨论】:

            【解决方案8】:

            尚未提及的差异:大小限制

            void *malloc(size_t size) 最多只能分配SIZE_MAX

            void *calloc(size_t nmemb, size_t size);可以分配大约SIZE_MAX*SIZE_MAX

            这种能力在许多具有线性寻址的平台中并不经常使用。此类系统将calloc() 限制为nmemb * size <= SIZE_MAX

            考虑一种名为disk_sector 的512 字节类型,并且代码想要使用很多 个扇区。在这里,代码最多只能使用SIZE_MAX/sizeof disk_sector 个扇区。

            size_t count = SIZE_MAX/sizeof disk_sector;
            disk_sector *p = malloc(count * sizeof *p);
            

            考虑以下允许更大分配的情况。

            size_t count = something_in_the_range(SIZE_MAX/sizeof disk_sector + 1, SIZE_MAX)
            disk_sector *p = calloc(count, sizeof *p);
            

            现在如果这样的系统能够提供如此大的分配是另一回事。大多数今天不会。然而,当 SIZE_MAX 为 65535 时,这种情况已经发生了很多年。鉴于 Moore's law,怀疑这将在 2030 年左右发生,其中某些内存模型具有 SIZE_MAX == 4294967295 和内存池为 100 GB。

            【讨论】:

            • 通常,size_t 将能够保存程序可以处理的最大对象的大小。 size_t 为 32 位的系统不太可能处理大于 4294967295 字节的分配,而能够处理该大小的分配的系统几乎肯定会使size_t 大于 32 位。唯一的问题是使用 calloc 与乘积超过 SIZE_MAX 的值是否可以依靠产生零而不是返回指向较小分配的指针。
            • 同意你的泛化,但 C 规范允许 calloc() 分配超过 SIZE_MAX。过去在 16 位 size_t 上发生过这种情况,随着内存的不断下降,我认为没有理由不会在未来发生这种情况,即使它不常见
            • C 标准使代码可以请求一个大小超过SIZE_MAX 的分配。它当然不要求存在这种分配可能成功的任何情况;我不确定强制无法处理此类分配的实现必须返回 NULL 有什么特别的好处(特别是考虑到某些实现通常有 malloc 返回指向尚未提交且可能不可用的空间的指针当代码实际尝试使用它时)。
            • 此外,过去可能存在可用寻址范围超过最大可表示整数的系统,我认为这种情况不会再次发生,因为这需要存储容量数十亿千兆字节。即使摩尔定律继续成立,从 32 位不再足够的点到 64 位不再足够的点所花费的时间是从 16 位足够的点到 32 位的点的两倍。 't。
            • 为什么可以容纳超过 4G 的单个分配的实现不定义 size_tuint64_t
            【解决方案9】:

            calloc一般malloc+memset为0

            显式使用malloc+memset 通常会稍微好一些,尤其是当您执行以下操作时:

            ptr=malloc(sizeof(Item));
            memset(ptr, 0, sizeof(Item));
            

            这更好,因为编译器在编译时就知道sizeof(Item),并且编译器在大多数情况下会用最好的指令替换它以使内存为零。另一方面,如果memset 发生在calloc 中,则分配的参数大小不会在calloc 代码中编译,并且经常调用真正的memset,它通常包含逐字节处理的代码-byte 填充到长边界,然后循环填充 sizeof(long) 块中的内存,最后逐字节填充剩余空间。即使分配器足够聪明,可以调用一些aligned_memset,它仍然是一个通用循环。

            一个值得注意的例外是当您对非常大的内存块(一些 power_of_two KB)进行 malloc/calloc 时,可以直接从内核进行分配。由于操作系统内核出于安全原因通常会将它们放弃的所有内存归零,因此足够聪明的 calloc 可能会在没有额外归零的情况下将其返回。再说一次 - 如果你只是分配一些你知道很小的东西,你可能会更好地使用 malloc+memset 性能。

            【讨论】:

            • +1 用于提醒系统库中功能的通用实现不一定比用户代码中的相同操作更快。
            • 还有第二点使calloc()malloc() 慢:大小的乘法。 calloc() 需要使用通用乘法(如果 size_t 是 64 位,即使是非常昂贵的 64 位*64 位=64 位操作),而 malloc() 通常会有一个编译时间常数。
            • glibc calloc 有一些聪明的方法来决定如何最有效地清除返回的块,例如有时只有部分需要清除,并且展开清除最多 9*sizeof(size_t)。内存就是内存,一次清除 3 个字节不会更快,因为您将使用它来保存struct foo { char a,b,c; };calloc 总是比malloc+memset 好,如果你总是要清除整个malloced 区域。 calloc 也对 size * 元素中的 int 溢出进行了仔细但有效的检查。
            【解决方案10】:

            calloc 的一个经常被忽视的优势是(一致的实现)它将帮助保护您免受整数溢出漏洞的影响。比较:

            size_t count = get_int32(file);
            struct foo *bar = malloc(count * sizeof *bar);
            

            对比

            size_t count = get_int32(file);
            struct foo *bar = calloc(count, sizeof *bar);
            

            如果count 大于SIZE_MAX/sizeof *bar,前者可能会导致很小的分配和随后的缓冲区溢出。在这种情况下,后者将自动失败,因为无法创建这么大的对象。

            当然,您可能必须注意不符合标准的实现,这些实现只是忽略了溢出的可能性...如果这是您所针对的平台上的一个问题,那么无论如何您都必须手动测试溢出.

            【讨论】:

            • 显然算术溢出是导致 OpenSSH 在 2002 年出现漏洞的原因。来自 OpenBSD 的关于内存相关函数的危险的好文章:undeadly.org/cgi?action=article&sid=20060330071917
            • @KomradeP.:有趣。可悲的是,您链接的文章一开始就有错误信息。 char 的示例不是溢出,而是在将结果分配回char 对象时实现定义的转换。
            • 它可能只是为了说明目的。因为编译器很可能无论如何都会优化它。我的编译成这个asm:push 1.
            • @tristopia:关键不是代码在所有实现上都可以利用,而是在没有额外假设的情况下它是不正确的,因此不正确/可移植。
            • @tristopia:如果您的思维方式是“size_t 是 64 位,所以这没问题”,这是一种有缺陷的思维方式,会导致安全漏洞。 size_t 是一种表示大小的抽象类型,没有理由认为 32 位数字和 size_t 的任意乘积(注意:sizeof *bar 原则上在 64- 上可能大于 2^32位 C 实现!)适合size_t
            【解决方案11】:

            分配的内存块大小没有区别。 calloc 只是用物理全零位模式填充内存块。在实践中,通常假设位于分配有calloc 的内存块中的对象具有初始值,就好像它们是用文字0 初始化的一样,即整数的值应该是0,浮点变量的值是0.0,指针 - 适当的空指针值,等等。

            不过,从迂腐的角度来看,calloc(以及memset(..., 0, ...))只能保证正确初始化(使用零)unsigned char 类型的对象。其他所有内容都不能保证正确初始化,并且可能包含所谓的陷阱表示,这会导致未定义的行为。换句话说,对于unsigned char 以外的任何类型,上述全零位模式可能表示非法值,即陷阱表示。

            后来,在 C99 标准的技术勘误之一中,为所有整数类型定义了行为(这是有道理的)。 IE。正式地,在当前的 C 语言中,您只能使用 calloc(和 memset(..., 0, ...))初始化整数类型。从 C 语言的角度来看,在一般情况下使用它来初始化其他任何东西都会导致未定义的行为。

            在实践中,calloc 有效,我们都知道 :),但是您是否要使用它(考虑到上述情况)取决于您。我个人更喜欢完全避免它,改用malloc 并执行我自己的初始化。

            最后,另一个重要的细节是calloc 需要通过将元素大小乘以元素数量来在内部计算最终块大小。在这样做的同时,calloc 必须注意可能的算术溢出。如果无法正确计算请求的块大小,将导致分配不成功(空指针)。同时,您的malloc 版本不会尝试监视溢出。如果发生溢出,它将分配一些“不可预测”的内存量。

            【讨论】:

            • 根据“另一个重要细节”段落:这似乎对memset(p, v, n * sizeof type); 造成了问题,因为n * sizeof type 可能会溢出。我想我需要使用 for(i=0;i<n;i++) p[i]=v; 循环来获得健壮的代码。
            • 如果有一个标准的方法可以帮助代码断言一个实现必须使用全位零作为空指针(否则拒绝编译),因为存在使用其他的实现空指针表示,但它们比较少见;如果可以使用 calloc() 或 memset 来初始化指针数组,则不必在此类实现上运行的代码会更快。
            • @chux 不,如果存在具有n 元素的数组,其中元素的大小为sizeof type,则n*sizeof type 不能溢出,因为任何对象的最大大小必须小于SIZE_MAX.
            • @12431234123412341234123 关于 array 大小是真的 SIZE_MAX,但这里没有 array。从calloc() 返回的指针可以指向超过SIZE_MAX 的已分配内存。许多实现确实将 2 个 args 的乘积限制为 calloc()SIZE_MAX,但 C 规范并未强加该限制。
            【解决方案12】:

            来自Benchmarking fun with calloc() and zero pagesGeorg Hager's Blog 上的一篇文章

            使用 calloc() 分配内存时,请求的内存量不会立即分配。相反,属于内存块的所有页面都通过某种 MMU 魔法连接到包含全零的单个页面(链接如下)。如果仅读取此类页面(在基准测试的原始版本中对数组 b、c 和 d 是正确的),则数据是从单个零页面提供的,这 - 当然 - 适合缓存。内存绑定的循环内核就这么多。如果一个页面被写入(无论如何),就会发生错误,映射“真实”页面并将零页面复制到内存中。这称为写时复制,这是一种众所周知的优化方法(我什至在我的 C++ 讲座中多次教授过这种方法)。在那之后,该页面的零读取技巧不再起作用,这就是为什么在插入了 - 据称是多余的 - 初始化循环后性能如此之低的原因。

            【讨论】:

              【解决方案13】:

              <stdlib.h> 标头中声明的calloc() 函数与malloc() 函数相比具有几个优点。

              1. 它将内存分配为给定大小的多个元素,并且
              2. 它初始化分配的内存,以便所有位都 零。

              【讨论】:

                【解决方案14】:

                一个鲜为人知的区别是,在具有乐观内存分配的操作系统(如 Linux)中,malloc 返回的指针在程序实际接触到它之前不会得到实际内存的支持。

                calloc 确实会触及内存(它会在其上写入零),因此您可以确定操作系统使用实际 RAM(或交换)支持分配。这也是为什么它比 malloc 慢的原因(它不仅必须将它归零,操作系统还必须通过可能换出其他进程来找到合适的内存区域)

                参见 this SO question 以进一步讨论 malloc 的行为

                【讨论】:

                • calloc 不需要写零。如果分配的块主要由操作系统提供的新零页组成,它可以保持这些不变。这当然需要将calloc 调整到操作系统而不是malloc 之上的通用库函数。或者,实现者可以让calloc 将每个单词与零进行比较,然后再将其归零。这不会节省任何时间,但可以避免弄脏新页面。
                • @R.. 有趣的注释。但在实践中,这样的实现是否存在于野外?
                • 如果块是通过 mmaping 新匿名页面(或等效)获得的,则所有类似 dlmalloc 的实现都会跳过 memset。通常这种分配用于较大的块,从 256k 左右开始。除了我自己的之外,我不知道有任何实现在写零之前与零进行比较。
                • omalloc 也跳过了memset; calloc 永远不需要接触任何尚未被应用程序使用的页面(页面缓存)。不过,extremely primitive calloc implementations 不同。
                • glibc 的 calloc 检查它是否从操作系统获取新内存。如果是这样,它知道它不需要编写它,因为 mmap(..., MAP_ANONYMOUS) 返回的内存已经归零。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-05-23
                • 2012-03-02
                • 2012-05-28
                • 2017-09-08
                相关资源
                最近更新 更多