C 什么构成工会中的填充?

C 什么构成工会中的填充?,c,language-lawyer,c11,C,Language Lawyer,C11,我试图解释当未显式初始化时,关于联合的静态(和线程本地)初始化 第6.7.9 10节(第139页)规定如下: 如果没有显式初始化具有自动存储持续时间的对象,则其值是不确定的。如果没有显式初始化具有静态或线程存储持续时间的对象,则: -如果有指针类型,则初始化为空指针 -如果它有算术类型,则初始化为(正或无符号)零 -如果它是一个聚合,则根据这些规则(递归地)初始化每个成员,并将任何填充初始化为零位 -如果它是一个并集,则根据这些规则(递归地)初始化第一个命名成员,并将任何填充初始化为零位 假设我

我试图解释当未显式初始化时,关于联合的静态(和线程本地)初始化

第6.7.9 10节(第139页)规定如下:

如果没有显式初始化具有自动存储持续时间的对象,则其值是不确定的。如果没有显式初始化具有静态或线程存储持续时间的对象,则:

-如果有指针类型,则初始化为空指针

-如果它有算术类型,则初始化为(正或无符号)零

-如果它是一个聚合,则根据这些规则(递归地)初始化每个成员,并将任何填充初始化为零位

-如果它是一个并集,则根据这些规则(递归地)初始化第一个命名成员,并将任何填充初始化为零位

假设我们使用的是amd64体系结构,给出以下声明:

static union { uint32_t x; uint16_t y[3]; } u;
可以
u.y[2]
包含非零值,或者因为它被视为填充而将其初始化为零?


我已经浏览了C11标准,但是对于什么构成工会中的填充,几乎没有任何解释。在填充中没有提到,因此在这种情况下,
u.y[2]
可以是非零。

如果存储是自动的,它可能包含任何值,因为它没有初始化。 如果存储器是静态的,它将初始化为零

填充不会影响联合,因为它是不属于结构或联合的任何成员的东西


例如,如果在您的实现中,数据被填充到8字节边界,则根本不会添加填充。此并集与下一个对象之间将有2个字节的间隔。

未被
x
使用的
y
所使用的额外空间不被视为填充。第6.7.2.1p17节“结构和联合规范”规定:

在结构或联合的末尾可能有未命名的填充

在您的示例中,
y
使用的、未被
x
使用的字节仍被命名,因此不是填充

您的示例很可能有这种未命名的填充,因为最大的成员占用6个字节,但其中一个成员是
uint32\t
,通常需要4字节对齐。事实上,在GCC4.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
  • 与y[2]相对应的字节4-5保持未初始化,并且具有不确定值
  • 与填充相对应的字节6-7被初始化为0
我在gcc 4.8.5、clang 3.3和MSVC 2015上对此进行了测试,在各种优化设置下,它们都将all字节设置为0。但是,严格阅读标准并不能保证这种行为,因此这些编译器的不同优化设置、不同版本或不同的编译器可能会做不同的事情

从实用的角度来看,编译器只需将静态对象的所有字节设置为0即可满足此要求。当然,这是假设没有整数类型具有填充,浮点类型为IEEE754,空指针的数值为0。在大多数人可能遇到的大多数系统上,情况就是这样。情况并非如此的系统可能更倾向于将这些字节设置为0以外的值。因此,尽管这些字节可能被设置为0,但也不能保证

需要记住的一点是,根据6.7.2.1p16,工会一次只能存储一名成员:

工会的规模足以容纳其最大的成员at的值 大多数成员可以随时存储在联合对象中。指向联合对象的指针 经过适当转换的union对象指向其每个成员(或者如果成员是位- 字段,然后发送到它所在的单元),反之亦然

因此,如果未初始化具有静态存储持续时间的
联合
,则只能安全地访问第一个成员,因为它是隐式初始化的成员

唯一的例外是,如果联合体包含具有一组公共初始成员的结构,在这种情况下,您可以访问内部结构的任何公共元素。第6.5.2.3p6节对此进行了详细说明:

为了简化工会的使用,有一个特别的保证:如果工会包含 几个结构共享一个共同的初始序列(见下文),如果联合 对象当前包含其中一个结构,允许它检查公共 它们中任何一个的起始部分,即完整的联合类型声明 是可见的。两个结构共享一个 公共初始序列 如果对应的成员 具有一个或多个序列的兼容类型(对于位字段,具有相同的宽度) 最初的成员

u.y[2]是否可以包含非零值,或者是否因为被视为填充而将其初始化为零?

u.y[2]
不被视为填充。它是数组
y
的一个元素,是union
u
的成员

联合体的大小仅与容纳其最大成员所需的大小相同(也可以添加额外的未命名的尾随填充物,以便于保存)

来自C标准#6.7.2.1p17

17在结构或接头的末端可能有未命名的填充物

最大的内存
#include <inttypes.h>

int main() {
        static union { uint32_t x; uint16_t y[3]; } u;
}
# 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.