sna*_*ppy 5 c language-lawyer c11
我正在尝试解释关于未明确初始化时联合的静态(和线程局部)初始化的C11标准.
第6.7.9节10(第139页)规定如下:
如果未显式初始化具有自动存储持续时间的对象,则其值不确定.如果未显式初始化具有静态或线程存储持续时间的对象,则:
- 如果它有指针类型,则将其初始化为空指针;
- 如果它有算术类型,则初始化为(正或无符号)零;
- 如果它是一个聚合,则根据这些规则初始化(递归)每个成员,并将任何填充初始化为零比特;
- 如果它是一个联合,则根据这些规则初始化(递归)第一个命名成员,并将任何填充初始化为零位;
假设我们使用amd64架构,给出以下声明:
static union { uint32_t x; uint16_t y[3]; } u;
Run Code Online (Sandbox Code Playgroud)
可以u.y[2]包含非零值,还是初始化为零,因为它被视为填充?
我已经仔细研究了C11的标准,但是关于什么构成联盟中的填充,几乎没有任何解释.在C99标准(第126页)中没有提到填充,因此在这种情况下u.y[2]可以是非零.
通过使用额外的空间y未被使用的x是不考虑填充.关于"结构和联合说明符" 的C11标准第6.7.2.1.117节规定:
结构或联合的末尾可能有未命名的填充
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
-----
Run Code Online (Sandbox Code Playgroud)
所以通过严格阅读标准,对于没有显式初始化器的此联合的静态实例:
x(即第一个命名成员)的字节0-3 被初始化为0,从而导致x为0.我在gcc 4.8.5,clang 3.3和MSVC 2015上进行了测试,所有这些都在各种优化设置下将所有字节设置为0.但是,通过严格阅读标准,行为无法得到保证,因此这些编译器的不同优化设置,它们的不同版本或不同的编译器仍然可能会做出不同的事情.
从实用的角度来看,编译器只需将静态对象的所有字节设置为0即可满足此要求.这当然假设没有整数类型具有填充,浮点类型是IEEE754,而NULL指针的数值是0.在大多数人可能遇到的大多数系统上都是如此.不是这种情况的系统可能更有可能将这些字节设置为0以外的其他值.再次,虽然这些字节可能设置为0,但不能保证.
需要记住的一点是,工会只能按照6.7.2.1p16一次存储一个成员:
联合的大小足以包含其最大的成员. 最多一个成员的值可以随时存储在union对象中. 指向适当转换的联合对象的指针指向其每个成员(或者如果成员是位字段,则指向它所在的单元),反之亦然.
因此,如果union具有静态存储持续时间的未初始化,则访问第一个成员是安全的,因为这是隐式初始化的成员.
唯一的例外是,如果union包含具有一组公共初始成员的结构,在这种情况下,您可以访问内部结构的任何公共元素.这在6.5.2.3p6节中详细说明:
为了简化联合的使用,我们做了一个特殊的保证:如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查公共其中任何一个的初始部分都可以看到完整类型的联合声明.如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列.
P__*_*J__ -1
如果存储是自动的,则它可能包含任何值,因为它未初始化。如果存储是静态的,它将初始化为零。
填充不会影响您的联合,因为它不属于结构或联合的任何成员。
例如,如果在您的实现中数据被填充到 8 字节边界,则根本不会添加任何填充。该联合和下一个对象之间将有 2 个字节的间隙。