0 和 calloc 之间的区别?

Posted

技术标签:

【中文标题】0 和 calloc 之间的区别?【英文标题】:Difference between 0 and calloc?0 和 calloc 之间的区别? 【发布时间】:2014-10-30 22:07:37 【问题描述】:

正如this answer on another question 所述,使用聚合初始化

struct foo 
    size_t a;
    size_t b;
;

struct foo bar = 0;

导致内置类型被初始化为零。

使用上面和使用有什么区别

struct foo * bar2 = calloc(1, sizeof(struct foo));

暂且不说一个变量是指针这一事实。 查看调试器我们可以看到,对于上述两个示例,ab 确实都设置为零。

以上两个例子有什么区别,有什么陷阱或隐藏的问题吗?

【问题讨论】:

现在我很好奇 - 如果我们将 calloc 替换为 alloca 后跟 memset 为零会怎样?那么会有什么明显的区别吗? alloca 是非标准的。此外,alloca 在堆栈上分配,而不是在堆上分配,并且它不会报告错误(分配比可用内存更多的内存具有未定义的行为,并且可能会导致您的程序在检测到错误之前崩溃)。 "它不报错" -- 调用函数也不报错,这也会破坏堆栈。该标准的一个技术缺陷是它不保证堆栈深度,因此几乎没有 C 程序严格符合。 如果您正在寻找有关初始化类型的答案,那么我建议您将问题更改为“0 和 memset 之间的差异”,然后执行 struct foo bar2; memset(&bar2, 0, sizeof bar2); "那么会有什么明显的不同吗?" -- 两者都将所有位设置为零,这在理论上与将它们设置为零值不同(参见 Deduplicator 和 Keith Thompson 的答案)。 【参考方案1】:

是的,有一个关键的区别(除了 struct foo 类型的对象的存储类):

struct foo bar = 0;
struct foo * bar2 = calloc(1, sizeof *bar2);

bar 的每个成员都初始化为零(对于没有初始化器的子对象,或者如果 bar 属于 staticthread_local 存储类,则填充将被置零), 而所有*bar2 都被清零,这可能会产生完全不同的结果:

空指针 (T*)0 和值为 0 的浮点数都不能保证全为 0。 (实际上,仅适用于charunsigned charsigned char(以及来自<stdint.h> 的一些可选的精确大小类型)保证所有位0 匹配值0 直到一段时间在 C99 之后。后来的技术勘误保证它适用于所有整数类型。)

浮点格式可能不是 IEEE754。

(在大多数现代系统上,您可以忽略这种可能性。)

引用c-faq(感谢 Jim Balter for linking it):

Prime 50 系列使用段 07777,空指针偏移量 0,至少对于 PL/I。

【讨论】:

对于struct foo bar = 0;,我认为不能保证填充为零。可能会,因为这样更容易实现——而且几乎可以肯定,在由两个 size_t 成员组成的结构中不会有任何填充。 @BillyONEal 我知道这是在最新编辑之前,但不是在说“完全不同的结果:空指针 (T*)0 和值为 0 的浮点数都不是”之前保证全为 0。”所以我的问题是。 “但在大多数现代系统上,你可以忽略这种可能性。” -- 是的,尽管这样的机器已经存在并且可能仍然存在:c-faq.com/null/machexamp.html @Billy:我最初的帖子已经说过空指针和浮点 0 不必是全位 0。不过,我完全重新表述了这一点,所以我怀疑他是否真的引用了我的第一个版本;-)。 所以回顾一下我是否理解正确,0 将结构成员设置为任何适当的零值位表示,而calloc 只是将所有位设置为零? 【参考方案2】:
struct foo bar = 0;

这定义了一个名为barstruct foo 类型的对象,并将其初始化为零。

“零”是递归定义的。所有整数子对象初始化为0,所有浮点子对象初始化为0.0,所有指针都初始化为NULL

struct foo * bar2 = calloc(1, sizeof(struct foo));

恕我直言,这更好(但等效)写成:

struct foo *bar2 = calloc(1, sizeof *bar2);

通过不重复类型名称,我们避免了以后更改代码时出现不匹配的风险。

这会动态分配struct foo 类型的对象(在堆上),将该对象初始化为所有位为零,并将bar2 初始化为指向它。

calloc 可能无法分配内存。如果是,则返回一个空指针。你应该经常检查。 (bar的声明也分配​​了内存,但是如果失败就是栈溢出,没有好办法处理。)

并且所有位为零保证与“零”相同。对于整数类型(包括size_t),几乎可以保证。对于浮点和指针类型,0.0NULL 具有除全位为零之外的一些内部表示是完全合法的。您不太可能遇到这种情况,并且由于您的结构中的所有成员都是整数,您可能不需要担心它。

【讨论】:

感谢您的回答,也感谢您提供有关分配语法的提示。您能否详细说明在什么情况下您的最后一段会出现问题?问题中显示的结构只是一个随机示例。 @Nit:实际上,在您可能使用的任何系统上,这都不太可能成为问题。我认为我实际上从未见过一个 C 实现,它对空指针或浮点 0.0 使用除全位零以外的任何东西。另一方面,就风格而言,我喜欢编写既实用又可移植的代码(但不再如此)。很多时候,更便携的代码会变得更简单,而且它让你少担心一件事。 "几乎可以保证" -- 有什么例外? @JimBalter:标准保证(从 C99 后的技术勘误之一开始)全位零是任何整数类型的 0 的表示。这并不意味着它是0唯一 表示。原则上,struct foo bar = 0; 可以bar.abar.b 设置为 0 的不同表示,calloc() 所做的。这几乎是不可能的。在该 C99 TR 之前,原则上可能全零位甚至不是0 的表示。委员会大概可以随意添加明确的要求,因为... @traducerad callocmemset 一样,会将所有内容初始化为全位 0,包括任何填充。但是“当一个值存储在结构或联合类型的对象中时,包括在成员对象中,对应于任何填充字节的对象表示的字节采用未指定的值。” -- C11 6.2.6.1 第 6 段。通常填充字节无关紧要,因此语言不会竭尽全力保证它们的稳定性。认为它们很脆弱。【参考方案3】:

calloc 给你一个堆dynamically allocated 归零内存区(到你的bar2)。但是在call stack 上分配了一个自动变量(如bar,假设它的声明在函数内部)。另见calloc(3)

在 C 中,您需要显式地free 堆分配内存区域。但是堆栈分配的数据会在其函数返回时弹出。

在C dynamic memory allocation 和garbage collection 上也重新阅读维基页面。 Reference counting 是 C 和 C++ 中广泛使用的技术,可以视为 GC 的一种形式。想想circular references,他们很难处理。

Boehm conservative GC 可以在 C 程序中使用。

请注意,内存区域的活跃度是一个全局程序范围的属性。您通常不能声称给定区域属于特定功能(或库)。但是您可以采用约定

当您编写一个返回堆分配指针(即指向动态存储的指针)的函数时,您应该记录这一事实并决定谁负责释放它。

关于初始化:calloc 指针归零(当calloc 成功时)。初始化为0 的自动变量也被归零。在实践中,某些实现可能会calloc 不同的大对象(通过从内核向它们询问整个零页面,例如使用mmap(2))和小对象(通过重用,如果有的话,以前的free-d 区域和归零它)。将区域归零正在使用 memset(3) 的快速等效项

PS。我忽略了所有零位内存区域不是 C 标准的清除数据的奇怪机器,即像 0。在实践中我不知道这样的机器,即使我知道它们原则上是可能的(理论上NULL 指针可能不是全零位字)

顺便说一句,编译器可能会优化一个全零的局部结构(并且可能根本不在堆栈上分配它,因为它适合寄存器)。

【讨论】:

如果您正在查看标准,动态分配 == 具有动态存储持续时间的对象,“在调用堆栈上”== 具有自动存储持续时间的对象。 感谢您的回答,这个问题不是一般的内存管理,而是两种初始化方法的功能差异。【参考方案4】:

(这个答案主要关注初始化的区别,在struct只包含整数类型的情况下)

两种形式都将ab 设置为0。这是因为标准定义整数类型的所有位为零必须表示值0

如果有结构填充,则calloc 版本会设置,但零初始化可能不会。例如:

struct foo a =  0 , b =  0 ;
struct foo c, d; memset(&c, 0, sizeof c); memset(&d, 0, sizeof d);

if ( memcmp(&a, &b, sizeof a) )
    printf("This line may appear.\n");

if ( memcmp(&c, &d, sizeof c) )
    printf("This line must not appear.\n");

您有时会看到的一种技术(尤其是在为适合具有少量存储的系统而设计的代码中)是使用memcmp 来比较两个结构是否相等。当结构成员之间存在填充时,这是不可靠的,因为即使结构成员相同,填充也可能不同。

程序员不想单独比较结构成员,因为代码量太大,所以相反,他将使用memcpy 复制结构,使用memset 初始化它们;为了保留使用memcmp 来检查相等性的能力。


在现代编程中,我强烈建议不要这样做;并始终使用 0 形式的初始化。后者的另一个好处是不会出现 size 参数出错并意外设置过多内存或过少内存的机会。

【讨论】:

【参考方案5】:

有一个严重的区别:自动变量的分配是在编译时完成的,并且是免费的(当堆栈帧被保留时,空间就在那里。)相反,动态分配是在运行时完成的,并且具有不可预测且不可忽略的成本。

关于初始化,编译器有机会使用自动变量进行优化(例如,在不必要的情况下不清除);调用 calloc 是不可能的。

如果你喜欢 calloc 风格,你还可以选择对自动变量执行 memset。

memset(&bar, 0, sizeof bar);

更新:自动变量的分配是准在编译时完成的。

【讨论】:

-1 自动变量的分配和初始化都不会在编译时发生……怎么可能呢? “调用 calloc 是不可能的”——是的,它是,因为 calloc 是一个标准函数,它的行为是由标准指定的,所以编译器可以依赖它。 我的反对是真诚和恰当的,我对此的解释是有效的。你不欢迎它是无关紧要的,除了违背 SO 的精神。 “如果严格来说编译器不分配内存空间”——事实编译器不分配内存空间,它只生成分配该空间的代码。初始化同上。将其称为“严格来说”意味着说 anything 在运行时发生是“严格来说”,因为编译器在编译时生成了代码。

以上是关于0 和 calloc 之间的区别?的主要内容,如果未能解决你的问题,请参考以下文章

C中堆管理——浅谈malloc,calloc,realloc函数之间的区别

C ++中的“new”和“malloc”和“calloc”有啥区别? [复制]

malloc 和 calloc 与 std::string 的区别

alloc()malloc()calloc()realloc()区别及用法

calloc() 和 NULL

malloc realloc calloc的区别