struct foo {
size_t a;
size_t b;
};
struct foo bar = {0};
结果是内置类型被初始化为零。
使用上述方法和使用
struct foo * bar2 = calloc(1, sizeof(struct foo));
暂且不考虑一个变量是指针的事实。
通过调试器,我们可以看到上述两个示例中,a
和b
都被设置为零。
上述两个示例有什么区别?是否存在任何陷阱或隐藏问题?
是的,除了您的struct foo
对象的存储类之外,还有一个关键的区别:
struct foo bar = {0};
struct foo * bar2 = calloc(1, sizeof *bar2);
bar
的成员都进行了零初始化(对于没有初始化器的子对象,或者如果 bar
是 static
或 thread_local
存储类,则填充也会被清零),
而所有的 *bar2
都被清零,这可能会产生完全不同的结果:(T*)0
和具有值 0 的浮点数不能保证是全零比特。事实上,只有在 C99 后的某个时候,对于 char
、unsigned char
和 signed char
(以及来自 <stdint.h>
的一些可选的精确大小类型),才保证所有比特为零与值 0 匹配。稍后的技术勘误为所有整数类型保证了这一点。Prime 50 系列至少对于 PL/I 使用 段 07777,偏移量为 0 的空指针。
struct foo bar = {0};
,我不认为有任何保证填充是零。它可能会是零,因为这样实现起来更容易 - 并且在由两个 size_t
成员组成的结构中几乎肯定不会有任何填充。 - Keith Thompson{0}
将结构成员设置为零值的适当位表示,而calloc
则将所有位设置为零,对吗? - Etherytestruct foo bar = {0};
bar
的struct 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.0
或NULL
完全可以有一些内部表示不同于全零位。你不太可能遇到这个问题,而且由于结构体的所有成员都是整数,所以你可能不需要担心它。
calloc
函数可以为你在堆上动态分配一个零初始化的内存区域(给你的bar2
)。但是自动变量(比如bar
,假设它的声明在函数内部)是分配在调用栈上的。另请参见calloc(3)。
在C语言中,需要显式地free
堆分配的内存区域。但是栈分配的数据在其函数返回时会被弹出。
(这个回答主要涉及结构体中只包含整数类型时初始化的差异)
这两种形式都将a
和b
设置为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 }
的初始化形式。后者的另一个好处是没有机会出错,因为大小参数不会被错误地设置为太多或太少的内存。有一个严重的区别:自动变量的分配是在编译时完成的,并且是免费的(当堆栈帧被保留时,空间就在那里)。相反,动态分配是在运行时完成的,具有不可预测和不可忽略的成本。
关于初始化,编译器可以通过自动变量进行优化(例如,如果不必要,则不清除);这在调用calloc时是不可能的。
如果您喜欢calloc样式,还可以选择对自动变量执行memset。
memset(&bar, 0, sizeof bar);
更新:自动变量的分配在编译时几乎完成。
alloca
替换calloc
,然后接着用memset
清零,会有什么显著的不同吗? - MooseBoysalloca
是非标准的。此外,alloca
在栈上分配内存,而不是在堆上,并且它不报告错误(分配比可用内存更多的内存具有未定义行为,并且很可能会导致程序在检测到错误之前崩溃)。 - Keith Thompsonstruct foo bar2; memset(&bar2, 0, sizeof bar2);
。 - M.M