什么在联合体中被认为是填充?

6
我正在尝试解释关于静态(和线程本地)初始化联合的 C11标准,当没有显式初始化时。
第6.7.9条款10(第139页)规定如下:
如果未显式初始化具有自动存储期的对象,则其值是不确定的。如果未显式初始化具有静态或线程存储期,则:
- 如果它具有指针类型,则将其初始化为null指针; - 如果它具有算术类型,则将其初始化为(正数或无符号)零; - 如果它是一个聚合体,则每个成员都根据这些规则进行(递归)初始化,并且任何填充都初始化为零位; - 如果它是一个联合体,则第一个命名成员根据这些规则进行(递归)初始化,并且任何填充都初始化为零位;
假设我们在amd64架构上,给出以下语句:
static union { uint32_t x; uint16_t y[3]; } u;

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

我已经仔细查阅了C11标准,但是对于联合中的填充内容没有太多解释。在C99标准(第126页)中未提及填充,因此在这种情况下u.y[2]可以是非零值。


1
你是指 u.y[3] 吗?我的理解是,如果联合需要一些填充,它将被初始化为0。假设在您的架构上,u需要4字节对齐,而y [3]是6字节并需要2字节对齐,则最终对齐需要是8的倍数,因此您将有2字节的填充(实际上在u.y [3]处),这将是0。 - Phil1970
@PSkocik:我错了,当一个成员是一个数组且其对齐方式较少严格时,联合体可以在结尾处具有填充,但该数组未填充另一个成员所需的更严格的对齐方式所需的空间(并且该数组也比另一个成员大,因此该成员本身不填充所需的空间)。 - Eric Postpischil
1
标准中唯一明确提到联合本身(而不是其成员)填充的地方是在可能位于末尾的填充中,就像问题示例中的u.y[3]一样。作为C实现的使用者,我必须将这个问题所询问的段落解释为告诉我尾部填充被初始化为零,并且没有告诉我y中而不是x中的字节(因此u.y[2])被初始化为零。作为实现开发人员,我可能会采取预防措施,将所有字节都初始化为零。 - Eric Postpischil
3个回答

5

y使用的额外空间,不被x使用,并非被视为填充。关于“结构体和联合体说明符”的第6.7.2.1p17节在C11标准中声明:

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

您示例中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
            -----

根据标准的严格解释,对于没有显式初始化器的静态联合实例:
  • 字节0-3对应于x(即第一个命名成员),初始化为0,导致x为0。
  • 字节4-5对应于y [2],保持未初始化状态,并具有不确定值。
  • 字节6-7对应于填充,初始化为0。
我在gcc 4.8.5、clang 3.3和MSVC 2015上进行了测试,在各种优化设置下都将所有字节设置为0。然而,根据标准的严格解释,行为不能保证,因此这些编译器的不同优化设置、不同版本或完全不同的编译器可能会产生不同的结果。
从实用的角度来看,编译器将所有静态对象的字节都设置为0是有意义的,以满足此要求。当然,这是假设没有整数类型填充、浮点类型为IEEE754且空指针具有数值0的情况。在大多数人可能遇到的系统上,这将是正确的。在这种情况下,这些字节可能会被设置为0,但是并没有保证。
需要记住的一个重要点是,联合只能同时存储一个成员,如6.7.2.1p16所述:

联合的大小足以包含其成员中最大的一个。联合对象中最多只能存储一个成员的值。指向联合对象的指针,经过适当转换,可以指向其每个成员(或者如果成员是位域,则指向其中所在的单元),反之亦然。

因此,如果未初始化具有静态存储期的联合,则仅安全访问第一个成员,因为这是隐式初始化的成员。
唯一的例外是如果联合包含具有常见初始成员集的结构,则可以访问内部结构的任何公共元素。这在6.5.2.3p6中详细说明:
为了简化联合使用,有一个特殊的保证:如果一个联合包含多个结构体,这些结构体共享一个公共初始序列(见下文),并且如果联合对象当前包含其中一个结构体,则允许在任何可见联合类型已声明的地方检查它们中任何一个的公共初始部分。如果对应成员具有兼容类型(对于位域来说,也要求相同的宽度)的初始成员序列,则两个结构体共享一个公共初始序列。

联合体的字节在典型的实际系统上最初可能全部为零的另一个原因是:当具有静态存储期的对象可以使用所有零字节进行初始化时(在大多数常见系统上,这将包括指针和浮点类型,而不仅仅是整数),它很可能会被放置在“BSS”数据段中。(不确定线程持续时间。) - aschepler

0

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

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

该联合体的大小仅足以容纳其最大的成员(也可能添加未命名的尾部填充以实现对齐)。

来自C标准#6.7.2.1p17

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

联合体u的最大成员是uint16_t y [3]; 。因此,如果联合体u中有任何填充,则会在uint16_t y [3];成员之后发生1)

根据C11标准,具有静态或线程存储期且未显式初始化的联合对象,编译器应递归地初始化第一个命名成员和任何填充为零位。因此,您不应该对u.y [2]值做出任何假设,因为编译器只会初始化联合体中的第一个命名成员(递归),这在您的示例中是uint32_t x,并将任何填充为零位(#6.7.9p10)。
C标准没有提及数据段(初始化/未初始化)、堆栈、堆等。所有这些都是特定于架构/平台的。对于对象初始化,C标准仅指定要初始化为0和不要初始化的内容,并且不指定哪个存储期对象进入哪个段。标准规范适用于编译器,好的编译器应遵循它们。通常,初始化为0的静态数据放在.BSS(由符号块启动),非0初始化数据放在.DATA(数据段)中。因此,您可能会发现u.y [2]值为0,但这并不总是正确的情况。

1) 每个现代编译器都会根据架构自动使用数据结构填充。一些编译器甚至支持警告标志-Wpadded,它会生成有关结构填充的有用警告。这些警告帮助程序员在需要更有效的数据结构布局时进行手动处理。

-Wpadded

如果在结构中包含填充以对齐结构的元素或整个结构,则发出警告。有时,可以重新排列结构的字段以减少填充并使结构更小。

因此,如果您的编译器支持警告标志-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)。


-1

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

填充对于您的联合体不会产生影响,因为它不属于结构或联合体的任何成员。

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


1
“例如,如果在您的实现中数据被填充到8字节边界,则根本不会添加填充。在该联合体和下一个对象之间将存在2个字节的间隔。”- 这是不正确的。在您所引用的情况下,联合体实例的大小将为8点击此处查看实现示例。填充作为实例的一部分被考虑在内;而不是被推入某些空隙之中。因此,要么您的陈述并不准确,要么它没有被准确传达并需要进行一些修改。” - WhozCraig
@WhozCraig 但这不是标准意义上的填充。这取决于具体实现。https://ideone.com/krfqGr - 0___________

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接