malloc 和 calloc 的区别?

Difference between malloc and calloc?

提问人:user105033 提问时间:10/8/2009 最后编辑:Ninjakannonuser105033 更新时间:2/3/2023 访问量:638320

问:

doing和有什么区别:

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

和:

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

什么时候使用是个好主意,反之亦然?callocmalloc

C 卡洛克

评论

48赞 phuclv 2/17/2016
在 C 语言中,你不会强制转换 malloc 族的结果
9赞 chqrlie 10/26/2016
在 C 语言中,你可以更笼统地将上述内容写成:ptr = calloc(MAXELEMS, sizeof(*ptr));
8赞 ddddavidee 12/14/2016
一篇关于 calloc 和 malloc+memset 之间区别的有趣帖子 vorpus.org/blog/why-does-calloc-exist
2赞 lifebalance 4/10/2018
@ddddavidee,在我对网上这么多答案不满意之后,我也找到了那个博客。纳撒尼尔·史密斯(Nathaniel J. Smith)的分析值得100+的SO分。
0赞 MCCCS 10/10/2018
相关新闻: calloc() 总共可以分配超过 SIZE_MAX 个吗?

答:

1007赞 Fred Larson 10/8/2009 #1

calloc()为您提供一个零初始化缓冲区,同时保持内存未初始化状态。malloc()

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

这意味着内存仍然可以是“干净的”和延迟分配的,并且写入时复制映射到系统范围的零共享物理页。(假设系统具有虚拟内存。例如,在 Linux 上进行性能实验时,效果是显而易见的。calloc

有些编译器甚至可以将 malloc + memset(0) 优化为 calloc,但如果你想将内存归零,最好只在源代码中使用 calloc。(或者,如果您试图预先出错以避免以后出现页面错误,则该优化将挫败您的尝试。

如果你不打算在写入内存之前读取内存,请使用它,这样它就可以(可能)从其内部可用列表中给你脏内存,而不是从操作系统获取新页面。malloc


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

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

评论

263赞 Cascabel 10/8/2009
*alloc 变体是非常助记符的 - clear-alloc、memory-alloc、re-alloc。
52赞 Jonathan Leffler 10/8/2009
如果您要设置在分配的空间中使用的所有内容,请使用 malloc()。如果您要保留部分数据未初始化,请使用 calloc() - 将未设置的部分归零将是有益的。
285赞 Pavel Minaev 10/8/2009
calloc不一定更昂贵,因为操作系统可以做一些技巧来加快它的速度。我知道 FreeBSD 在获得任何空闲的 CPU 时间时,会使用它来运行一个简单的进程,该进程会四处走动并将释放的内存块清零,并用标志标记块,从而标记进程块。因此,当您这样做时,它首先会尝试找到这样的预归零块之一,然后将其提供给您 - 并且很可能会找到一个。calloc
32赞 Steve Jessop 10/8/2009
我倾向于认为,如果你的代码由于默认的零启动分配而变得“更安全”,那么无论你使用 malloc 还是 calloc,你的代码都不够安全。使用 malloc 是一个很好的指标,表明数据需要初始化 - 我只在这 0 个字节实际上有意义的情况下使用 calloc。另请注意,对于非 char 类型,calloc 不一定像您认为的那样。没有人真正使用陷阱表示,或者非IEEE浮点数,但这并不是认为你的代码是真正可移植的借口,而事实并非如此。
23赞 dennis 5/4/2014
@SteveJessop“更安全”不是正确的词。我认为“确定性”是更好的术语。更具确定性的代码,而不是依赖于时序和数据序列的故障,将更容易隔离故障。Calloc 有时是获得确定性的简单方法,而不是显式初始化。
395赞 Isak Savo 10/19/2009 #2

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

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

例如,请参阅此 SO 问题,以进一步讨论 malloc 的行为

评论

59赞 R.. GitHub STOP HELPING ICE 1/4/2011
calloc不需要写零。如果分配的块主要由操作系统提供的新零页组成,则可以保留这些零页。当然,这需要根据操作系统进行调整,而不是在 之上使用通用库函数。或者,实现者可以在将每个单词归零之前将其与零进行比较。这不会节省任何时间,但可以避免弄脏新页面。callocmalloccalloc
3赞 Isak Savo 1/4/2011
@R..有趣的笔记。但在实践中,这种实现是否存在于野外?
12赞 R.. GitHub STOP HELPING ICE 1/5/2011
如果块是通过新的匿名页面(或等效页面)获取的,则所有类似实现都会跳过。通常这种分配用于较大的块,从 256k 左右开始。除了我自己的实现之外,我不知道有任何实现在编写零之前与零进行比较。dlmallocmemsetmmap
1赞 mirabilos 4/1/2014
omalloc还跳过了 ; 永远不需要触摸应用程序尚未使用的任何页面(页面缓存)。但是,极其原始的 calloc 实现有所不同。memsetcalloc
10赞 Peter Cordes 12/8/2014
glibc 的 calloc 检查它是否从操作系统中获取新内存。如果是这样,它知道它不需要写入它,因为 mmap(..., MAP_ANONYMOUS) 返回已归零的内存。
27赞 AnT stands with Russia 10/19/2009 #3

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

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

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

在实践中,正如我们都知道的那样,:),但你是否想使用它(考虑到上述情况)取决于你。我个人更喜欢完全避免它,而是使用并执行我自己的初始化。callocmalloc

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

评论

0赞 chux - Reinstate Monica 2/14/2015
根据“另一个重要细节”段落:这似乎是一个问题,因为可能会溢出。我想我需要使用一个循环来编写健壮的代码。memset(p, v, n * sizeof type);n * sizeof typefor(i=0;i<n;i++) p[i]=v;
0赞 supercat 8/14/2016
如果有一种标准方法,代码可以断言实现必须使用全位零作为空指针(否则拒绝编译),那将很有帮助,因为存在使用其他空指针表示的实现,但它们相对较少;如果代码可以使用 calloc() 或 memset 来初始化指针数组,则不必在此类实现上运行的代码可能会更快。
0赞 12431234123412341234123 7/26/2017
@chux 不可以,如果存在一个包含元素的数组,其中元素的大小为 ,则不能溢出,因为任何对象的最大大小必须小于 。nsizeof typen*sizeof typeSIZE_MAX
0赞 chux - Reinstate Monica 7/26/2017
@12431234123412341234123关于数组大小为 <= 为 True ,但这里没有数组。返回的指针可以指向分配的内存 than exceed 。许多实现确实将 2 个参数的乘积限制为 ,但 C 规范并没有施加该限制。SIZE_MAXcalloc()SIZE_MAXcalloc()SIZE_MAX
0赞 Sandrious 8/9/2023
@AnT,“算术溢出”和“堆溢出”是一回事吗?
127赞 R.. GitHub STOP HELPING ICE 8/14/2010 #4

一个经常被忽视的优点是(一致的实现)它将有助于保护您免受整数溢出漏洞的侵害。比较: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);

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

当然,您可能必须注意不合规的实现,这些实现只是忽略了溢出的可能性......如果这是您目标平台上的问题,那么无论如何您都必须进行手动溢出测试。

评论

23赞 Philip P. 4/24/2014
显然,算术溢出是导致 2002 年 OpenSSH 漏洞的原因。来自 OpenBSD 的好文章,介绍了与内存相关的功能的危险: undeadly.org/cgi?action=article&sid=20060330071917
5赞 R.. GitHub STOP HELPING ICE 4/24/2014
@KomradeP.:有意思。可悲的是,您链接的文章一开始就存在错误信息。该示例不是溢出,而是将结果分配回对象时实现定义的转换。charchar
0赞 Philip P. 4/25/2014
它可能只是为了说明目的。因为编译器无论如何都可能会对其进行优化。我的编译成这个 asm:push 1。
1赞 R.. GitHub STOP HELPING ICE 9/21/2014
@tristopia:关键不在于代码在所有实现上都可以利用,而是在没有额外假设的情况下它是不正确的,因此不正确/可移植使用。
3赞 R.. GitHub STOP HELPING ICE 9/21/2014
@tristopia:如果你的思维模式是“是 64 位,所以没问题”,这是一种有缺陷的思维方式,会导致安全漏洞。 是表示大小的抽象类型,没有理由认为 32 位数字和 a(注意:在 64 位 C 实现中原则上可能大于 2^32!)的任意乘积适合 。size_tsize_tsize_tsizeof *barsize_t
10赞 Jainendra 9/14/2012 #5

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

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

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

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

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

7赞 Vipin Diwakar 7/31/2013 #6

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

  1. 它将内存分配为给定大小的多个元素,并且
  2. 它初始化分配的内存,以便所有位都 零。
43赞 t0rakka 8/15/2013 #7

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

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

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

22赞 aashoo 8/28/2013 #8

摘自 Georg Hager 博客上的一篇文章 Benchmarking fun with calloc() and zero pages

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

14赞 virco 8/17/2014 #9

calloc一般为0malloc+memset

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

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

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

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

评论

1赞 Patrick Schlüter 9/20/2014
+1 表示提醒系统库中功能的通用实现不一定比用户代码中的相同操作更快。
1赞 Patrick Schlüter 9/20/2014
还有第二点使速度慢于:大小的乘法。 需要使用通用乘法(如果是 64 位,即使是非常昂贵的 64 位*64 位=64 位操作),而 malloc() 通常具有编译时常量。calloc()malloc()calloc()size_t
5赞 Peter Cordes 12/8/2014
glibc calloc 有一些智能来决定如何最有效地清除返回的块,例如,有时只有部分需要清除,以及高达 9*sizeof(size_t) 的展开清除。内存就是内存,一次清除 3 个字节不会更快,因为您将使用它来保存 . 总是比 + 好,如果你总是要清除整个 ed 区域。 对大小 * 元素中的 int 溢出也进行了仔细但有效的检查。struct foo { char a,b,c; };callocmallocmemsetmalloccalloc
9赞 Shivaraj Bhat 1/12/2015 #10

区别1:

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

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

区别2:

如果考虑语法,则只需要 1 个参数。请看以下示例:malloc()

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

例如:如果要为 int 类型分配 10 个内存块,

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

如果考虑语法,则需要 2 个参数。请看以下示例:calloc()

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

例如:如果要为 int 类型分配 10 个内存块并将所有内存块初始化为 ZERO,

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

相似:

如果它们不是类型转换的,则默认情况下将返回 void*!malloc()calloc()

评论

0赞 Sold Out 3/14/2018
为什么你保持data_type和cast_type不同?
6赞 chux - Reinstate Monica 8/30/2015 #11

尚未提及的区别:尺寸限制

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

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

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

考虑一种称为 512 字节的类型,代码想要使用大量扇区。在这里,代码最多只能使用扇区。disk_sectorSIZE_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);

现在,这样的系统是否可以提供如此大的分配是另一回事。今天大多数人不会。然而,当 65535 年时,它已经发生了很多年。鉴于摩尔定律,怀疑这将发生在 2030 年左右,某些内存模型和内存池在 100 GB 中。SIZE_MAXSIZE_MAX == 4294967295

评论

2赞 supercat 8/14/2016
通常,size_t将能够容纳程序可以处理的最大对象的大小。size_t为 32 位的系统不太可能处理大于 4294967295 字节的分配,而能够处理该大小的分配的系统几乎肯定会大于 32 位。唯一的问题是,是否可以依靠与乘积超过的值一起使用来产生零,而不是返回指向较小分配的指针。size_tcallocSIZE_MAX
0赞 chux - Reinstate Monica 8/15/2016
同意您的泛化,但 C 规范允许分配超过 .过去在 16 位中发生过这种情况,随着内存不断降低,我认为没有理由它不会在未来发生,即使它不常见calloc()SIZE_MAXsize_t
1赞 supercat 8/15/2016
C 标准使代码可以请求大小超过 的分配。当然,它并不要求在某种情况下这种分配可能成功;我不确定强制要求无法处理此类分配的实现必须返回有什么特别的好处(特别是考虑到某些实现通常具有指向尚未提交的空间的返回指针,并且在代码实际尝试使用它时可能不可用)。SIZE_MAXNULLmalloc
0赞 supercat 8/15/2016
此外,如果过去可能有一些系统的可用寻址范围超过了最大可表示整数,我认为这种情况不会再次发生,因为这将需要数十亿千兆字节的存储容量。即使摩尔定律继续成立,从 32 位不再足够到64 位不再足够,所需的时间也是从 16 位足够到 32 位不够的两倍。
2赞 supercat 8/16/2016
为什么一个可以容纳超过4G的单一分配的实现不能定义为?size_tuint64_t
7赞 elmiomar 6/19/2018 #12

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

它们的原型如下:

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

两者之间主要有两个区别:

  • 行为:分配一个内存块,而不对其进行初始化,从该块读取内容将导致垃圾值。另一方面,分配一个内存块并将其初始化为零,显然读取该块的内容会导致零。malloc()calloc()

  • 语法:接受 1 个参数(要分配的大小),并接受 2 个参数(要分配的块数和每个块的大小)。malloc()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));

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

// 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()

19赞 Majbah Habib 6/22/2019 #13

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

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

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

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

  1. 字节

    • 要分配的字节数

calloc()接受 2 个参数:

  1. 长度

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

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

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

名称的含义:
名称的意思是“内存分配”。
该名称的意思是“连续分配”。
malloccalloc

评论

1赞 mohammadsdtmnd 10/31/2022
可爱的答案。简明扼要。
0赞 Akshay V 7/31/2020 #14

和分配内存,但将所有位初始化为零,而没有。malloccalloccallocmalloc

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

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