具有 32 位参数的 64 位编译器结构填充

64-bit compiler struct padding with 32-bit arguments

我一直在尝试了解 64 位编译器如何强制执行结构对齐,但我不明白为什么在结构只有 32 位参数的情况下没有填充。

我希望即使在那种情况下也有填充,因为 64 位 CPU 使用 64 位指针访问内存,不是吗?

   typedef struct {
       uint32_t a1;
       uint32_t a2;
       uint32_t a3;
   }tHeader;

  typedef struct{
      tHeader header;
      uint32_t data1;
      uint32_t data2;
  }tPacket1;



0               7   bytes
+-------+-------+
|  a1      a2   |
+-------+-------+
+-------+-------+
|  a3     data1 |       
+-------+-------+
+-------+-------+
| data2         |      <---- Why no padding here?
+-------+-------+
20 bytes.

存在 64 位参数时的填充示例:

   typedef struct {
       uint32_t a1;
       uint32_t a2;
       uint32_t a3;
       uint64_t a4;
   }tHeader;

  typedef struct{
      tHeader header;
      uint32_t data;
  }tPacket1;



0               7   bytes
+-------+-------+
|  a1      a2   |
+-------+-------+
+-------+-------+
|  a3    PADDING|       
+-------+-------+
+-------+-------+
|       a4      |
+-------+-------+
+-------+-------+
| data   PADDING|       <---- Why padding here?
+-------+-------+
Total: 8 * 4 = 32 bytes.

测试:

$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

第一种情况不需要填充,因为所有成员都有 4 字节对齐。所以如果你有两个连续的结构,它可以像这样布局:

0               7   bytes
+-------+-------+
|  a1      a2   |
+-------+-------+
+-------+-------+
|  a3     data1 |       
+-------+-------+
+-------+-------+
| data2    a1   |
+-------+-------+
+-------+-------+
|  a2      a3   |
+-------+-------+
+-------+-------+
| data1   data2 |       
+-------+-------+

但这在第二个示例中不起作用,因为 a4 需要 8 字节对齐。如果它在末尾省略了填充,你会得到这个:

0               7   bytes
+-------+-------+
|  a1      a2   |
+-------+-------+
+-------+-------+
|  a3    PADDING|       
+-------+-------+
+-------+-------+
|       a4      |
+-------+-------+
+-------+-------+
| data     a1|      
+-------+-------+
+-------+-------+
|  a2      a3   |
+-------+-------+
+-------+-------+
|PADDING   a4   |       
+-------+-------+
+-------+-------+
| a4      data  |
+-------+-------+

但是像这样拆分第二个 a4 是不允许的。

您可以使用 packed 属性来强制它。然后将使用多个 32 位指令访问 64 位元素。这也将避免 a3a4.

之间的填充

padding的思想是为了优化访问,而不是反复无常。 64位cpus 可以进行64位内存总线访问,但是当总线传输完成32位数量时,进行64位访问没有任何好处。当cpu要传输一个32位的数量时,它select向select64位寄存器发送一个64位的地址,并且只读取你select输入的数量对应的字节。当您有一个不是 8 的倍数地址的 64 位字段时,编译器会填充一个结构。如果您必须访问它,如果数据未与 8 地址的倍数对齐,cpu 应该必须对 load/store 数据进行两次 64 位访问。为此,进行了对齐。如果你有一个只有 32 位地址的结构,访问这些寄存器只需要 32 位总线访问,所以整个结构只需要 32 位对齐。

让我们走极端...假设您有一个字符寄存器。您是否应该进行 64 位总线访问并要求 char 与 64 位地址对齐以仅传输一个字节的数据?

为了计算对齐你必须考虑:

  • 我从结构中的最后一个字段得到的空闲 space 的偏移量是多少?
  • 下一个字段要求的对齐方式是什么?

让我们假设结构中打包的最后一个字段留下 13 个字节的偏移量(您使用了 char[13] 类型)并假设下一个要打包的字段是 char:一个 char 需要一个字节,因此它可以附加到最后一个字段而无需任何对齐(它的对齐方式为 1)。现在假设它是一个 short (两个字节),编译器应该把它放在下一个偶地址(偏移量 14,填充一个字节,这将允许结构需要偶地址对齐(或者 short 字段不会对齐)如果它是 32 位 int,那么下一个字段的偏移量将需要 4 地址的倍数,并且应该插入 3 个字节的填充,并且所以偏移量应该是 16,整个结构有 4 字节对齐要求。如果数据是 64 位整数,偏移量应该固定在 8 字节边界,使编译器插入 3 字节的填充以使它在偏移量 16 处,并且整个结构的对齐要求为 16 字节。当没有更多的字段留下来完成结构时,仍然有一些填充以使整个结构适合等于下一个倍数的整数字节结构的 要求对齐方式 ,因此如果您附加两个结构,就像在这种类型的结构数组中一样,排列是守恒的。所以如果我们在结构的末尾添加一个最终的 3 char 数组,我们需要添加一个填充字节来填充整个结构的 4 字节对齐。

所以,一旦这样说,填充是根据下一个字段的对齐要求计算的,基于该字段所需的对齐方式(如果它是一个数组,则所需的对齐方式与单个单元格相同数组的类型,如果是简单类型,则为类型本身的大小,对于结构,基本类型应为下一个具有结构最大对齐要求的字段的对齐要求)

在您显示的情况下,仅包含 32 位整数,因此它们之间不使用填充(如果结构对齐到 32 位,则所有字段将对齐到 32 位)并且在末尾不需要填充结构(在结构数组的情况下保持对齐)