如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));
撇开一个变量是指针的事实
查看调试器,我们可以看到,对于上述两个示例,a
和b
都确实设置为零。
上述两个例子之间有什么区别,是否存在任何问题或隐藏的问题?
答案 0 :(得分:4)
是的,除了struct foo
类型的对象的存储类外,还有一个重要区别:
struct foo bar = {0};
struct foo * bar2 = calloc(1, sizeof *bar2);
bar
的每个成员都是零初始化的(如果没有初始值设定项,或者bar
属于static
或thread_local
存储,则填充将归零-class),
虽然*bar2
的所有内容都已清零,但可能会产生完全不同的结果:
空指针(T*)0
和值为0的浮点数都不能保证为全位-0。
(实际上,仅适用于char
,unsigned char
和signed char
(以及<stdint.h>
中的一些可选的精确大小类型),保证全部位 - 0匹配值-0直到C99之后的某个时间。后来的技术勘误保证了所有积分类型。)
浮点格式可能不是IEEE754。
(在大多数现代系统中,你可以忽略这种可能性。)
引自c-faq(感谢Jim Balter for linking it):
Prime 50系列使用段07777,空指针偏移0 ,至少对于PL / I。
答案 1 :(得分:3)
calloc
为您提供了一个堆dynamically allocated归零内存区域(进入bar2
)。但是在call stack上分配了一个自动变量(如bar
,假设其声明在函数内)。另请参阅calloc(3)
在C中,您需要显式free
堆分配的内存区域。但是当函数返回时会弹出堆栈分配的数据。
Rerad还在C dynamic memory allocation和garbage collection上播放了wikipage。 Reference counting是C和C ++中广泛使用的技术,可以视为GC的一种形式。想想circular references,它们很难处理。
Boehm conservative GC可用于C程序。
请注意,内存区域的活跃度是全局程序范围的属性。您通常不能声称给定区域属于特定功能(或库)。但你可以采用约定。
当你编写一个返回堆分配指针的函数(即指向动态存储的指针)时,你应该记录这个事实并决定谁负责释放它。
关于初始化:calloc
指针归零(当calloc
成功时)。初始化为{0}
的自动变量也归零。在实践中,一些实现可能calloc
不同的大对象(通过向内核请求整个归零页面,例如使用mmap(2))和小对象(通过重用,如果可用,先前{{1} } -d zone并将其归零)。零区域使用快速等效的memset(3)
PS。我忽略了奇怪的机器,其中所有零位存储区不是C标准的清除数据,即像free
。我在实践中不知道这样的机器,即使我知道它们原则上是可能的(理论上{0}
指针可能不是一个全零位的字)
答案 2 :(得分:3)
struct foo bar = {0};
这定义了一个名为struct foo
的{{1}}类型的对象,并将其初始化为零。
&#34;零&#34;是递归定义的。所有整数子对象都初始化为bar
,所有浮点子对象都初始化为0
,所有指针都初始化为0.0
。
NULL
恕我直言,这更好(但等效地)写成:
struct foo * bar2 = calloc(1, sizeof(struct foo));
如果不重复类型名称,我们可以避免以后更改代码时出现不匹配的风险。
这会动态分配类型为struct foo *bar2 = calloc(1, sizeof *bar2);
的对象(在堆上),将该对象初始化为所有位为零,并初始化struct foo
以指向它。
bar2
无法分配内存。如果是,则返回空指针。你应该经常检查一下。 (calloc
的声明也会分配内存,但是如果它失败则会导致堆栈溢出,并且没有好办法处理它。)
并且所有位零是不保证与&#34;零&#34;相同。对于整数类型(包括bar
),它几乎得到保证。对于浮点数和指针类型,size_t
或0.0
完全合法,除了所有位零之外还有一些内部表示。你不太可能碰到这个,因为你结构的所有成员都是整数,你可能不需要担心它。
答案 3 :(得分:1)
(这个答案侧重于初始化的差异,如果struct
仅包含整数类型)
这两种表单都将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 }
形式的初始化。后者的另一个好处是没有机会在size参数中出错,并且意外地设置了太多的内存或太少的内存。
答案 4 :(得分:0)
存在一个严重的区别:自动变量的分配在编译时完成并且是免费的(当保留堆栈帧时,房间就在那里。)相反,动态分配在运行时完成,具有不可预测和不可忽视的成本。
关于初始化,编译器有机会使用自动变量进行优化(例如,如果不需要则不清除);调用calloc是不可能的。
如果您喜欢calloc样式,还可以选择在自动变量上执行memset。
memset(&bar, 0, sizeof bar);
更新:自动变量的分配是在编译时准备完成的。