做以下两种操作有何区别:
ptr = malloc(MAXELEMS * sizeof(char *));
并且:
ptr = calloc(MAXELEMS, sizeof(char*));
何时使用calloc
比malloc
更好,反之亦然?
calloc()
会为您提供一个零初始化的缓冲区,而 malloc()
则会保留内存未初始化。
对于大型分配,大多数主流操作系统下的 calloc
实现将从操作系统获取已知的零值页面(例如通过 POSIX 的 mmap(MAP_ANONYMOUS)
或 Windows 的 VirtualAlloc
),因此它不需要在用户空间中编写它们。这也是正常的 malloc
从操作系统获取更多页面的方式;calloc
只是利用了操作系统的保证。
这意味着 calloc
内存仍然可以是“干净”的和惰性分配的,并且可以复制到全局共享的物理零页面上。(假设具有虚拟内存的系统。)例如,在 Linux 上进行 性能实验 就可以看到这种效果。
一些编译器甚至可以为您优化 malloc + memset(0) 以转换为 calloc,但最好在源代码中直接使用 calloc 来获得零化内存。(或者,如果您尝试预先故障它以避免稍后出现页面故障,则该优化将破坏您的尝试。)
如果您永远不打算在写入内存之前读取它,请使用 malloc
,这样它可以(可能)为您提供来自其内部空闲列表的脏内存,而不是从操作系统获取新页面。(或者,为小型分配从空闲列表上的内存块中清零一个内存块。)
嵌入式calloc的实现可能会将清零内存的任务交给calloc本身完成,如果没有操作系统,或者不是一款高级的多用户操作系统,则可能会出现这种情况以防止进程之间的信息泄漏。
在嵌入式Linux中,malloc
可以使用mmap(MAP_UNINITIALIZED|MAP_ANONYMOUS)
,但这只适用于某些嵌入式内核,因为在多用户系统中它是不安全的。
calloc
不一定更昂贵,因为操作系统可以使用一些技巧来加快速度。我知道FreeBSD会在闲置的CPU时间内运行一个简单的进程,它只是循环清零已释放的内存块,并标记这些进程使用的内存块。因此,当您使用 calloc
函数时,它首先尝试找到这些预清零的块之一并将其分配给您-很可能会找到一个这样的块。 - Pavel Minaev一个不太为人所知的区别是,在像Linux这样具有乐观内存分配的操作系统中,malloc
返回的指针在程序实际访问它之前并没有得到真正的内存支持。
calloc
确实会访问内存(它会写入零),因此您可以确信操作系统正在使用实际的RAM(或交换空间)来支持该分配。这也是为什么它比malloc慢的原因(它不仅需要将其清零,而且操作系统还必须找到一个合适的内存区域,可能会使其他进程被交换出去)
关于malloc行为的更多讨论,请参见此SO问题
calloc
不必写零。如果分配的块主要由操作系统提供的新的零页面组成,它可以保持那些页面不变。当然,这需要 calloc
调整到操作系统上,而不是在 malloc
上实现的一个通用库函数。另外,实现者也可以在对每个字进行清零之前将其与零进行比较。这样做不会节省任何时间,但它可以避免污染新页面。 - R.. GitHub STOP HELPING ICEdlmalloc
的实现,如果通过mmap
映射新的匿名页面(或其等效方式)获取了内存块,则会跳过memset
操作。通常这种分配用于较大的内存块,从256k开始。除了我的实现之外,我不知道有任何实现在写零之前对零进行比较。 - R.. GitHub STOP HELPING ICEsize_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
。后者将自动在这种情况下失败,因为无法创建如此大的对象。char
相关的例子并不是溢出,而是将结果重新分配回一个char
对象时的实现定义转换。 - R.. GitHub STOP HELPING ICEsize_t
,因为当size_t
宽度为64位时,我并没有看到任何问题。 - Patrick Schlütersize_t
是64位的,所以没有问题”,那就是一种有缺陷的思维方式,这将导致安全漏洞。size_t
是一个抽象类型,表示大小,没有理由认为32位数字和size_t
(注意:在64位C实现中,“sizeof *bar
”的值原则上可能大于2^32)的任意乘积适合size_t
。 - R.. GitHub STOP HELPING ICE在分配的内存块大小上没有区别,calloc
只是用物理零位模式填充内存块。实际上,通常假设使用 calloc
分配的内存块中的对象具有初始值,就好像它们被字面值 0
初始化一样,即整数应该具有值 0
,浮点变量应具有值 0.0
,指针应具有适当的空指针值等。
然而,严格来说,calloc
(以及 memset(..., 0, ...)
)仅保证正确地初始化了类型为 unsigned char
的对象。其他所有内容都不能保证被正确初始化,并且可能包含所谓的“trap representation”,这会导致行为未定义。换句话说,对于除了 unsigned char
之外的任何类型,前面提到的全零位模式可能表示非法值,即陷阱表示。
后来,在 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 - Reinstate Monican
个元素的数组,其中一个元素具有大小sizeof type
,那么n*sizeof type
不会溢出,因为任何对象的最大大小必须小于SIZE_MAX
。 - 12431234123412341234123SIZE_MAX
的数组是正确的,但这里没有数组。从calloc()
返回的指针可能指向超过SIZE_MAX
的分配内存。许多实现确实将2个参数的乘积限制为calloc()
的SIZE_MAX
,但C规范并未强制执行该限制。 - chux - Reinstate Monica这篇文章来自于Georg Hager's Blog,名为Benchmarking fun with calloc() and zero pages。
使用calloc()分配内存时,请求的内存量不会立即分配。相反,所有属于内存块的页面都通过一些MMU魔法连接到一个包含所有零的单个页面上(下面有链接)。如果仅读取这些页面(在原始版本的基准测试中是数组b、c和d),则数据从单个零页面提供,当然适合缓存。至此,关于内存限制的循环内核就讲完了。如果写入页面(无论如何)会导致故障,将映射“真实”页面并将零页面复制到内存。这被称为写时复制,这是一种众所周知的优化方法(我甚至在我的C++讲座中多次教授)。之后,对于该页面,零读取技巧将不再起作用,这就是为什么在插入 - 可能是冗余的 - 初始化循环后性能要低得多的原因。
块数:
malloc()
分配单个请求的内存块,
calloc()
分配多个请求的内存块。
初始化:
malloc()
- 不清除和初始化分配的内存。
calloc()
- 将分配的内存初始化为零。
速度:
malloc()
很快。
calloc()
比 malloc() 慢。
参数和语法:
malloc()
接受一个参数:
字节数
calloc()
接受两个参数:
长度
字节数
void *malloc(size_t bytes);
void *calloc(size_t length, size_t bytes);
内存分配方式:
malloc
函数从可用的堆中分配所需“size”大小的内存。
calloc
函数分配大小为“num * size”的内存。
名称含义:
malloc
的名字意味着“内存分配”。
calloc
的名字意味着“连续分配”。
calloc
通常相当于malloc+memset
设置为0。
明确使用malloc+memset
通常稍微更好,尤其是在做以下操作时:
ptr=malloc(sizeof(Item));
memset(ptr, 0, sizeof(Item));
这样更好,因为编译器在编译时知道sizeof(Item)
的大小,并且编译器通常会用最优代码将其替换为清零内存的最佳指令。另一方面,如果memset
在calloc
中发生,则分配的大小参数不会在calloc
代码中编译,并且通常会调用真正的memset
,其中包含逐字节填充到长边界,然后循环以sizeof(long)
块填充内存,最后逐字节填充剩余空间的代码。即使分配器足够聪明地调用某些aligned_memset
,它仍然是一个通用的循环。
有一个值得注意的例外情况,那就是当您需要malloc/calloc一个非常大的内存块(一些2的幂次方千字节)时,此时的分配可能直接从内核中完成。由于操作系统内核通常会清零所有提供的内存以确保安全性,因此聪明的calloc可能直接返回未经过额外清零处理的内存。同样地,如果您只是要分配一些小内存,那么在性能方面使用malloc+memset可能更好。
calloc()
比malloc()
慢:大小的乘法。 calloc()
需要使用通用乘法(如果size_t
是64位,则甚至需要进行非常昂贵的64位* 64位= 64位操作),而malloc()
通常具有编译时常量。 - Patrick Schlüterstruct foo { char a, b, c; }
。如果你总是要清除整个malloc分配的内存区域,那么calloc总是比malloc+memset更优。此外,calloc还有一个仔细但高效的检查int溢出的方法,用于sizeelements的计算。 - Peter Cordes这里有两个区别。
首先是参数个数,malloc()
只需要一个参数(所需内存的字节数),而calloc()
需要两个参数。
其次,malloc()
不会初始化分配的内存,而calloc()
会将分配的内存初始化为0。
calloc()
分配一块内存区域,其长度将是其参数的乘积。 calloc
用0填充内存并返回第一个字节的指针。如果无法定位足够的空间,则返回一个NULL
指针。语法:ptr_var = calloc(no_of_blocks, size_of_each_block);
即:ptr_var = calloc(n, s);
malloc()
分配一个请求大小的单块内存,并返回第一个字节的指针。如果它无法找到请求的内存量,则返回一个空指针。语法:ptr_var = malloc(Size_in_bytes);
malloc()
函数需要一个参数,该参数是要分配的字节数,而calloc()
函数需要两个参数,一个是元素数量,另一个是要为每个元素分配的字节数。此外,calloc()
会将分配的空间初始化为零,而malloc()
则不会。
区别1:
malloc()
通常分配内存块并初始化内存段。
calloc()
分配内存块并将所有内存块初始化为0。
区别2:
如果您考虑malloc()
语法,它只需要1个参数。请考虑下面的示例:
data_type ptr = (cast_type *)malloc( sizeof(data_type)*no_of_blocks );
例如:如果你想要分配 10 个 int 类型的内存块,
int *ptr = (int *) malloc(sizeof(int) * 10 );
data_type ptr = (cast_type *)calloc(no_of_blocks, (sizeof(data_type)));
例如:如果您想为 int 类型分配 10 个内存块,并将所有内容初始化为零,
int *ptr = (int *) calloc(10, (sizeof(int)));
相似性:
如果没有进行类型转换,malloc()
和 calloc()
默认会返回 void* 类型。
malloc
系列函数的返回值进行类型转换。 - phuclvptr = calloc(MAXELEMS, sizeof(*ptr));
。其中,calloc
用于动态分配内存,MAXELEMS
表示需要分配的元素个数,sizeof(*ptr)
表示每个元素所占内存的大小,ptr
表示指向分配内存的指针。 - chqrlie