工会中的填充物是什么?

时间:2019-01-12 13:41:36

标签: c language-lawyer c11

我试图解释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]可以为非零。

3 个答案:

答案 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。
  • 与y [2]对应的字节4-5保持未初始化,并具有不确定值
  • 与填充相对应的字节6-7初始化为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选项进行编译。我的编译器是clangclang-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个字节的间隙。