malloc和calloc之间的区别?


779

两者之间有什么区别:

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

要么:

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

什么时候在malloc上使用calloc是个好主意,反之亦然?



8
在C语言中,您可以将以上内容写为:ptr = calloc(MAXELEMS, sizeof(*ptr));
chqrlie

7
关于calloc和malloc + memset之间的区别的有趣帖子vorpus.org/blog/why-does-calloc-exist
ddddavidee

2
@ddddavidee我对网络上的许多答案都不满意之后,也找到了该博客。Nathaniel J. Smith的分析值得获得100多分。
lifebalance '18

Answers:


849

calloc()给您一个零初始化的缓冲区,而未初始化malloc()的内存。

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

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

一些编译器甚至可以为您优化malloc + memset(0)到calloc中,但是如果您希望将内存读取为,则应显式使用calloc 0

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


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

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


224
* alloc变体非常易记-清除分配,内存分配,重新分配。
卡斯卡贝尔

43
如果要设置分配空间中使用的所有内容,请使用malloc()。如果要保留部分数据未初始化,请使用calloc()-将未设置的部分清零将是有益的。
乔纳森·勒夫勒

268
calloc不一定更贵,因为OS可以采取一些技巧来加快速度。我知道FreeBSD在获得空闲的CPU时间时,会使用它来运行一个简单的过程,该过程会四处循环,并将释放的内存块清零,并使用标志将这些块标记为进程。因此,当您这样做时calloc,它首先尝试找到这样的预调零块之一,然后将其提供给您-而且很可能会找到一个。
帕维尔·米纳夫

28
我倾向于认为,如果由于默认情况下零初始化分配而使您的代码变得“更安全”,则无论使用malloc还是calloc,您的代码都不够安全。使用malloc可以很好地指示数据是否需要初始化-仅在那些0字节实际上有意义的情况下才使用calloc。另请注意,对于非字符类型,calloc不一定会按照您的想法进行操作。没有人真正使用陷阱表示形式了,也没有人使用非IEEE浮点型,但这并不是认为代码真正可移植的借口。
Steve Jessop

18
@SteveJessop“更安全”不是正确的词。我认为“确定性”是更好的术语。具有确定性的代码比具有取决于时序和数据序列的故障的代码将更容易隔离故障。与显式初始化相比,Calloc有时是获得确定性的简便方法。
丹尼斯2014年

362

一个鲜为人知的区别是,在具有乐观内存分配的操作系统(例如Linux)中,malloc直到程序真正接触到它之后,返回的指针才由实内存支持。

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

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


49
calloc不需要写零。如果分配的块主要由操作系统提供的新零页组成,则可以使那些原样保留。当然,这需要calloc调整到操作系统,而不是在之上的通用库函数malloc。或者,实现者可以calloc将每个单词与零进行比较,然后再将其清零。这不会节省任何时间,但可以避免弄脏新页面。
R .. GitHub停止帮助ICE,

3
@R ..有趣的笔记。但是实际上,这样的实现是否存在?
Isak Savo

10
如果通过新的匿名页面(或等效页面)获得了块,则所有dlmalloc类似的实现都将跳过。通常,这种分配用于较大的块,从256k左右开始。我不知道有什么实现可以在我自己写零之前先对零进行比较。memsetmmap
R .. GitHub停止帮助ICE,

1
omalloc也跳过了memset; calloc永远不需要触摸应用程序尚未使用的任何页面(页面缓存)。但是,极其原始的calloc实现方式有所不同。
mirabilos 2014年

10
glibc的calloc检查它是否正在从操作系统中获取新的内存。如果是这样,它知道不需要编写它,因为mmap(...,MAP_ANONYMOUS)返回已经为零的内存。
彼得·科德斯

111

一个经常被忽略的优点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。在这种情况下,后者将自动失败,因为无法创建较大的对象。

当然,您可能需要寻找不符合标准的实现,而这些实现只是忽略了溢出的可能性...如果您所针对的平台存在此问题,则无论如何都必须手动进行溢出测试。


17
显然,算术溢出是导致2002年OpenSSH漏洞的原因。OpenBSD
Philip P.

4
@KomradeP .:有趣。不幸的是,您链接的文章一开始就包含错误信息。与例如char分配的结果返回到一个当溢出而是实现定义的转换char的对象。
R .. GitHub停止帮助ICE 2014年

它可能仅用于说明目的。因为编译器很可能会优化它。矿编译成该ASM:推1
菲利普P.

1
@tristopia:重点不是该代码可在所有实现上利用,而是在没有附加假设的情况下它是不正确的,因此不是正确/可移植的用法。
R .. GitHub停止帮助ICE 2014年

3
@tristopia:如果您的思维方式是“ size_t是64位的,那没问题”,那是一种有缺陷的思维方式,将导致安全漏洞。size_t是代表大小的抽象类型,没有理由认为32位数字和a size_t(注意:sizeof *bar在64位C实现上原则上可以大于2 ^ 32!)的任意乘积适合size_t
R .. GitHub停止帮助ICE 2014年

37

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

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

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


26

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

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

后来,在C99的技术勘误中之一,为所有整数类型定义了行为(这是有道理的)。即,在形式上,在当前的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;循环来生成健壮的代码。
chux-恢复莫妮卡2015年

如果有一种标准的方法可以使代码断言某个实现必须使用全零位作为空指针(否则,则拒绝编译),这将很有帮助,因为存在使用其他空指针表示形式的实现,但是它们是比较稀有 如果可以使用calloc()或memset初始化指针数组,则不必在此类实现上运行的代码会更快。
超级猫

@chux否,如果n存在带有元素的数组且元素具有大小sizeof type,则n*sizeof type不会溢出,因为任何对象的最大大小必须小于SIZE_MAX
12431234123412341234123

@ 12431234123412341234123 数组大小<=正确SIZE_MAX,但此处没有数组。从返回的指针calloc()可以指向已分配的内存,其数目超过了SIZE_MAX。许多实现的确将2个args的乘积限制calloc()SIZE_MAX,但是C规范没有强加该限制。
chux-恢复莫妮卡

21

摘自Georg Hager博客用calloc()和零页进行基准测试的乐趣

使用calloc()分配内存时,请求的内存量不会立即分配。取而代之的是,某些MMU魔术将属于该内存块的所有页面连接到包含全零的单个页面(下面的链接)。如果仅读取此类页面(在基准的原始版本中,数组b,c和d确实如此),则从单个零页面提供数据,这当然适合缓存。对于绑定内存的循环内核来说就这么多。如果页面被写入(无论如何),都会发生错误,“实际”页面将被映射,零页面将被复制到内存中。这称为写时复制,这是一种众所周知的优化方法(我什至在我的C ++讲座中都曾多次教过它)。之后,


链接在哪里?
Rupesh Yadav。

2
第一行答案包含指向Georg Hager博客的链接。
Ashish Chavan

11

calloc一般malloc+memset为0

通常最好malloc+memset显式使用,特别是在执行以下操作时:

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

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

一个值得注意的例外是,当您对非常大的内存块(某些power_of_两千字节)执行malloc / calloc时,这种情况下可以直接从内核完成分配。由于操作系统内核通常会出于安全原因将它们释放的所有内存清零,因此足够聪明的calloc可能会在没有额外清零的情况下将其返回。再说一次-如果您只是分配一些您知道的很小的东西,那么在性能方面使用malloc + memset可能会更好。


+1提醒我们,系统库中功能的一般实现不一定比用户代码中的相同操作快。
PatrickSchlüter2014年

1
还有第二点要calloc()慢于malloc():大小相乘。calloc()需要使用通用乘法(如果size_t是64位,即使是非常昂贵的64位* 64位= 64位操作),而malloc()通常具有编译时间常数。
PatrickSchlüter2014年

4
glibc calloc有一些聪明的人来决定如何最有效地清除返回的块,例如,有时只需要清除其中的一部分,以及展开展开到9 * sizeof(size_t)的清除。内存就是内存,一次将其清除3个字节并不会因为要使用它来保持而更快struct foo { char a,b,c; };。 如果您总是要清除整个ed区域,calloc它总是比malloc+ 好。 也对大小*元素的int溢出进行了仔细但有效的检查。memsetmalloccalloc
彼得·科德斯

8

差异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个内存块,并将所有内存初始化为ZERO,

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

相似:

双方malloc()calloc()会默认返回void *,如果他们不是类型铸造!


为什么要保持data_type和cast_type不同?
脱销

7

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

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

语法:ptr_var=(cast_type *)calloc(no_of_blocks , size_of_each_block); ieptr_var=(type *)calloc(n,s);

  • malloc()分配REQUSTED SIZE的单个内存块,并返回指向第一个字节的指针。如果找不到所需的内存量,它将返回空指针。

语法:ptr_var=(cast_type *)malloc(Size_in_bytes);malloc()函数取一个参数,它是字节数来分配,而calloc()函数有两个参数,其中之一是元件的数量,而另一个是字节数分配为每个元件。同样,calloc()将分配的空间初始化为零,而malloc()不会初始化为零。


6

calloc()是在声明的函数<stdlib.h>头提供了几个优点,在malloc()功能。

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

6

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));

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()改用。


5

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

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_sector代码想要使用很多扇区。在这里,代码最多只能使用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。考虑到摩尔定律,怀疑这种情况将在2030年左右发生,某些内存模型的SIZE_MAX == 4294967295内存池为100 GB。


2
通常,size_t将能够容纳程序可以处理的最大类型的对象的大小。size_t为32位的系统不太可能处理大于4294967295字节的分配,而能够处理该大小的分配的系统几乎肯定会size_t大于32位。唯一的问题是,是否可以依靠使用calloc乘积超过的值SIZE_MAX产生零,而不是返回指向较小分配的指针。
超级猫

同意您的概括,但是C规范允许calloc()分配超出的值SIZE_MAX。它过去曾经发生在16位,size_t并且随着内存的持续廉价,我认为没有理由即使它不常见也不会发生。
chux-恢复莫妮卡

1
C标准使代码可以请求大小超过的分配SIZE_MAX。当然,它不要求在任何情况下这种分配都可以成功;我不确定强制执行无法处理此类分配的实现是否会带来任何特别的好处NULL(尤其是鉴于某些实现通常具有malloc尚未提交且在代码实际尝试使用时可能不可用的空间的返回指针)它)。
超级猫

此外,在过去可能存在可用寻址范围超过最大可表示整数的系统的地方,我看不到有任何现实的可能性再次发生,因为这将需要数十亿千兆字节的存储容量。即使摩尔定律继续成立,从32位不再足够的点到64位不再足够的点,要从16位足够到32位的点花费两倍的时间。没错
超级猫

1
为什么可以容纳超过4G的单个分配的实现不定义size_tuint64_t
超级猫

2

块数:
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表示“连续分配”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.