首页 > 解决方案 > 什么构成联合中的填充?

问题描述

我试图解释C11 标准关于在未明确初始化时联合的静态(和线程本地)初始化。

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

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

— 如果它具有指针类型,则将其初始化为空指针;

— 如果它具有算术类型,则将其初始化为(正或无符号)零;

— 如果是聚合,则每个成员都根据这些规则(递归地)初始化,并且任何填充都被初始化为零位;

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

假设我们在 amd64 架构上,给定以下语句:

static union { uint32_t x; uint16_t y[3]; } u;

可以u.y[2]包含非零值还是初始化为零,因为它被视为填充?

我已经搜索了 C11 标准,但几乎没有解释什么构成联合中的填充。在C99 标准(第 126 页)中没有提到填充,所以在这种情况下u.y[2]可以是非零的。

标签: clanguage-lawyerc11

解决方案


y未被使用的额外空间x视为填充。C11 标准中关于“结构和联合说明符”的第 6.7.2.1p17 节规定:

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

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

您的示例很可能确实有这个未命名的填充,因为最大的成员占用 6 个字节,但其中一个成员是 auint32_t通常需要 4 个字节对齐。事实上,在 gcc 4.8.5 上,这个联合的大小是 8 个字节。所以这个 union 的内存布局是这样的:

            -----  --|       ---|
         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 上对此进行了测试,它们都在各种优化设置下将所有字节设置为 0。但是,严格阅读标准并不能保证行为,因此这些编译器的不同优化设置、它们的不同版本或完全不同的编译器可能会做不同的事情。

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

要记住的重要一点是,根据 6.7.2.1p16,联合一次只能存储一个成员:

工会的规模足以容纳其最大的成员。 任何时候最多可以将其中一个成员的值存储在联合对象中。 一个指向联合对象的指针,经过适当的转换,指向它的每个成员(或者如果一个成员是位域,则指向它所在的单元),反之亦然。

因此,如果union具有静态存储持续时间的 a 未初始化,则仅访问第一个成员是安全的,因为这是隐式初始化的成员。

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

一个特殊的保证是为了简化联合的使用:如果联合包含多个共享相同初始序列的结构(见下文),并且如果联合对象当前包含这些结构之一,则允许检查公共它们中的任何一个的初始部分,在任何地方都可以看到已完成联合类型的声明。如果对应的成员对于一个或多个初始成员的序列具有兼容的类型(并且对于位域,具有相同的宽度),则两个结构共享一个共同的初始序列。


推荐阅读