为什么存储在 struct 和 union 对象中的值对应于任何填充字节采用未指定的值?

Why value stored in struct and union object correspond to any padding bytes take unspecified values?

来自 C11 标准

我刚刚在 C11 标准中读到这个,但无法理解为什么存储在 struct/union 的对象中的值,填充字节的对象表示采用未指定的值?

我知道未指定的意思是标准没有强加任何要求。


When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values.

The value of a structure or union object is never a trap representation, even though the value of a member of the structure or union object may be a trap representation

谁能给我一个例子来更好地理解这个说法?

嗯,问题是你永远不知道结构或联合的元素是如何事先确定在内存中的。填充位可能因系统而异。

您也可以考虑编译器对此的处理方法。实现布局它们的优化是不同的。在某些编译器中,它将是一个,然后在另一个编译器中会有所不同。

陷阱表示是适合某种类型所占 space 的位模式,但如果用作该类型的值,则会触发未定义的行为。

例如:-

struct a{
   int a;
   char b;
};

这里结构的大小不一定是ab的大小。正在添加填充,这是标准不评论的事情。您希望将结构作为一个整体来处理,表示有效。但是如果你想访问具有严格 8 字节表示的元素,它可能会失败。

假设sizeof(long)=8sizeof(int)=4,64位机器:

struct {
    int a;
    long b;
} obj = {0, 0};

&obj.a&obj.b 之间填充 4 个字节是有意义的。 padding的内容应该是什么?为什么要强制运行时将任何东西放在那里? obj 的布局也可能是 0x00000000, 0xDEADBEEF, 0x00000000, 0x00000000 - 即 4 个字节的垃圾,因为它们不需要被访问(因此不应该)。

一般来说,问题是相反的 - 如果您认为应该指定它(即您想要强制编译器编写者做更多的工作,并且可能效率较低的工作),那么您应该解释原因。

基本上是这样的:

  • 要使成员正确对齐,struct/union 需要填充字节。编译器会根据需要自动插入这些。
  • C 委员会没有对填充字节必须具有什么值提出任何要求。这是有意为之的,因此无论何时初始化或复制结构,程序都不需要写入填充字节。如果标准要求填充字节具有例如零值,那么这将引入非常小的执行开销。

    (这种非常微小的潜在性能提升是结构具有所有这些晦涩的机制的原因。这确实是 C 的精神 - 如果我们可以以混淆为代价使某些东西稍微快一点和不一致,那就这样吧。)

  • 但是,C 还允许外来系统具有陷阱表示 - 某些位序列会在读取时产生某种 运行 时间异常。如果允许填充字节具有任何值,它们可能最终会成为陷阱表示。因此,有一个例外说填充字节可能有任何值,但不是陷阱表示的值。

因此:您不能相信填充字节具有任何给定值(除非它们不能是陷阱表示)。填充字节的值可能因情况而异 - 不能保证它们的值是一致的。

考虑一个没有陷阱表示的普通 32 位二进制补码系统:

typedef struct
{
  uint8_t  u8;
  uint32_t u32;
} something_t;

something_t thing1 = {1, 2};
something_t thing2 = {3, 4}

这里,一种可能的内存布局是这样的(十六进制,小端):

01 AA BB CC 02 00 00 00  // thing 1
03 55 66 77 04 00 00 00  // thing 2
^  ^        ^
u8 padding  u32

在 thing1 中,01 是 u8 成员,AA BB CC 序列用未指定的值填充字节,02 00 00 00 是 u32 成员。

如果我们现在写thing1 = thing2,允许编译器执行以下操作之一:

  • 复制整个thing2并覆盖所有thing1,包括填充,如果这是最有效的,
  • 如果效率更高,复制u8u32但不要写入填充字节,让它们保持原样,导致thing1 现在具有内存布局 03 AA BB CC 04 00 00 00.

这就是为什么我们既不能将结构与 == 运算符进行比较,也不能将结构与 memcmp().

等函数进行比较的原因