我试图解释C11 standard,涉及未明确初始化的联合的静态(和线程局部)初始化。
第6.7.9 10节(第139页)规定:
如果未自动初始化具有自动存储期限的对象,则其值不确定。如果未明确初始化具有静态或线程存储持续时间的对象,则:
-如果具有指针类型,则将其初始化为空指针;
-如果具有算术类型,则将其初始化为(正数或无符号)零;
-如果是集合,则根据这些规则初始化(递归)每个成员,并将任何填充初始化为零位;
-如果是联合,则将根据这些规则(递归地)初始化第一个命名成员,并将任何填充初始化为零位;
假设我们使用的是amd64架构,则给出以下声明:
static union { uint32_t x; uint16_t y[3]; } u;
u.y[2]
可以包含非零值还是因为被视为填充而将其初始化为零?
我已经仔细检查过C11标准,但是对于什么是联合中的填充几乎没有解释。在C99 standard (pg 126)中未提及填充,因此在这种情况下,u.y[2]
可以为非零。
答案 0 :(得分:5)
y
未使用的x
使用的多余空间被 not 视为填充。 C11 standard中有关“结构和联合说明符”的第6.7.2.1p17节规定:
结构或联合的末尾可能存在未命名的填充
您的示例中y
所使用的x
未使用的字节仍然被命名,因此不会被填充。
您的示例很可能确实具有此未命名的填充,因为最大的成员占用6个字节,但是成员之一是uint32_t
,通常需要4个字节对齐。实际上,在gcc 4.8.5上,此联合的大小为8个字节。因此,该联合的内存布局如下所示:
----- --| ---|
0 | 0 | | |
----- | |-- y[0]
1 | 0 | | |
----- |-- x ---|
2 | 0 | | |
----- | |-- y[1]
3 | 0 | | |
----- --| ---|
4 | 0 | |
----- |-- y[2]
5 | 0 | |
----- ---|
6 | 0 | -- padding
-----
7 | 0 | -- padding
-----
因此,请严格阅读标准,以获取没有显式初始化程序的该联合的静态实例:
x
(即第一个命名成员)的字节0-3被初始化为0,导致x
为0。我在gcc 4.8.5,clang 3.3和MSVC 2015上对此进行了测试,并且它们在各种优化设置下都将 all 字节设置为0。但是,严格地阅读标准并不能保证其行为,因此这些编译器的不同优化设置,它们的不同版本或完全不同的编译器仍然可能会有所不同。
从务实的角度来看,对于编译器而言,将静态对象的所有字节简单地设置为0以满足此要求是有意义的。当然,这是假设没有整数类型具有填充,浮点类型是IEEE754,而NULL指针的数值为0。在大多数人可能会遇到的大多数系统上,都是这种情况。并非如此的系统可能会将这些字节设置为非0的值。因此,再次将这些字节可能设置为0时,并不能保证。
要记住的重要一点是,根据6.7.2.1p16,工会一次只能存储一个成员:
工会的规模足以容纳其最大的成员。 at的值 大多数成员可以随时存储在联合对象中。 经过适当转换的并集对象指向其每个成员(或者,如果某个成员有点位, 字段,然后转到其所在的单元),反之亦然。
因此,如果具有静态存储期限的union
未初始化,则仅安全访问 first 成员,因为该成员是隐式初始化的。
唯一的例外是,如果联合包含具有一组公共初始成员的结构,则在这种情况下,您可以访问内部结构的任何公共元素。第6.5.2.3p6节对此进行了详细说明:
为了简化联合的使用,做出了一个特殊保证:如果联合包含 具有共同初始序列的几个结构(请参见下文),以及 对象当前包含这些结构之一,因此可以检查通用对象 声明联合完成类型的任何地方的初始部分 是可见的。两个结构共享一个 共同的初始序列 如果有相应成员 具有一个或多个序列的兼容类型(对于位字段,具有相同的宽度) 初始成员。
答案 1 :(得分:0)
Can u.y[2] contain non-zero values or is it initialised to zero because it is regarded as padding?
u.y[2]
不被视为填充。它是数组y
的元素,是数组u
的成员。
工会的大小只能容纳其最大的会员(为alignment的目的,也可以添加未命名的尾随填充)。
来自C标准#6.7.2.1p17
17在结构或联合的末尾可能存在未命名的填充。
联合体u
的最大成员是uint16_t y[3];
。因此,如果联合u
中有任何填充,那么它将在uint16_t y[3];
成员 1)之后。
根据C11标准,具有静态或线程存储持续时间且未显式初始化的联合对象,编译器应(递归地)初始化第一个命名成员,并将其填充为零位。因此,您不应该对u.y[2]
的值做任何假设,因为编译器将只初始化union 2)的第一个命名成员,即uint32_t x
在您的示例中,以及填充到零位的所有内容(#6.7.9p10)。
C标准未提及有关数据段(已初始化/未初始化),堆栈,堆等的任何内容。这些都是特定于体系结构/平台的。对于对象初始化,C标准仅指定要初始化为0
的内容,不指定要初始化的内容,并且不指定哪个存储持续时间对象进入哪个段。 Standard规范是针对编译器的,并且期望遵循良好的编译器。通常,0
初始化的静态数据进入.BSS(以符号开头的块),非0
初始化的数据进入.DATA(数据段)。因此,您可能会发现u.y[2]
的值0
,但并非总是如此。
1)每个现代的编译器都会根据体系结构自动使用数据结构填充。一些编译器甚至支持警告标志-Wpadded
,该标志会生成有关结构填充的有用警告。这些警告可帮助程序员进行手动维护,以防需要更有效的数据结构布局。
-Wpapped
警告填充是否包含在结构中,以对齐结构的元素或对齐整个结构。有时,当发生这种情况时,可以重新排列结构的字段以减少填充,从而使结构变小。
因此,如果您的编译器支持警告标记-Wpadded
,请尝试使用它来编译代码。这将帮助您了解编译器随附的填充。
例如
#include <inttypes.h>
int main() {
static union { uint32_t x; uint16_t y[3]; } u;
}
可以使用-Wpadded
选项进行编译。我的编译器是clang
版clang-1000.10.44.4
# clang -Wpadded p.c
p.c:4:16: warning: padding size of 'union (anonymous at p.c:4:16)' with 2 bytes to alignment boundary [-Wpadded]
static union { uint32_t x; uint16_t y[3]; } u;
^
1 warning generated.
2)要注意的一点-如果您显式初始化并集对象,除非它是指定的初始化,否则也将初始化并集的第一个成员(C11标准#6.7.9p17)。
答案 2 :(得分:-1)
如果存储是自动的,由于未初始化,它可能包含任何值。 如果存储是静态的,它将初始化为零。
填充不影响您的联合,因为它不属于结构或联合的任何成员。
例如,如果在您的实现中将数据填充到8个字节的边界,则根本不会添加填充。此联合与下一个对象之间将有2个字节的间隙。