【问题标题】:Why does compiler align N byte data types on N byte boundaries?为什么编译器在 N 字节边界上对齐 N 字节数据类型?
【发布时间】:2014-09-12 10:35:42
【问题描述】:

我不明白为什么编译器在 4 字节边界上对齐 int,在 2 字节边界上对齐 short,在 1 字节边界上对齐 char。 我知道,如果处理器的数据总线宽度为 4 个字节,则需要 2 个内存读取周期才能从地址不是 4 的倍数读取 int。
那么,为什么编译器不在 4 字节边界上对齐所有数据呢? 例如:

struct s {
 char c;
 short s;
};

这里, 1)为什么编译器在 2 字节边界上对齐短?假设处理器可以在单个内存读取周期中获取 4 个字节,那么即使 char 和 short 之间没有填充,在上述情况下读取 short 也只需要 1 个内存读取周期吗?

2) 为什么编译器不在 4 字节边界上对齐 short?

【问题讨论】:

  • Purpose of memory alignment 的可能重复项
  • 结构填充对齐的目的是在一台机器读取中获取数据。在您的情况下,结构将是 4 而不是 8。您仍然可以使用掩码在一个周期内获取 char OR short 。因此,在获取 char 时,处理器将获取 4 个字节并屏蔽 24 位。
    但是,如果您有这样的事情:
    struct s { char c; int i}; 那么大小将变为 8 个字节,因为您需要完整的 4 个字节要在读取周期中获取的整数。
  • @NikhilVidhani:我的问题不是关于填充的目的。我的问题是关于为什么字节在 char 和 short 之间而不是在 short 之后填充。假设处理器可以在单个周期内获取 4 个字节,那么无论填充发生在哪里,短时间都可以在 1 个周期内获取,对吗?那么,在上述情况下,我们节省了多少?我想对此有一些硬件级别的解释。
  • @linuxfreak 凭我的直觉...我认为如果 short 占用字节 2 和 3,获取(掩码)最后 16 位比 9-24 位更容易。
  • @NikhilVidhani - 是的……我想是的。要获取 9-24 位,处理器除了屏蔽之外还必须进行位移。

标签: c memory compiler-construction memory-alignment


【解决方案1】:

这些对象必须适合数组。数组是连续的。因此,如果第一个元素是 N 字节对齐的,并且所有对象都是 N 字节大,那么数组中的所有对象也必然是 N 字节对齐的。

因此,如果short 的大小为 2 个字节,但 4 个字节对齐,则数组中的所有短裤之间将有 2 个字节的空洞,这是被禁止的。

您确实看到您的假设存在轻微缺陷。我可以用 26 个字符制作一个 struct,它不会是 26 字节对齐的。它可以从任何地方开始。一个 N 字节类型,其对齐等于 N 或除以 N

【讨论】:

  • @iccthedral:绝对不是。 26 字节类型可能是 2 字节对齐的,但不是 52 字节对齐的。
  • 为什么短2字节对齐?不管上述结构中char和short之间是否添加了填充字节,cpu只需要1个内存读取周期。那么,为什么 char 和 short 之间要填充一个字节呢?也就是说,上面结构的对齐方式反正是4,为什么不把short放在char下面,不留一个字节给padding呢?
  • 我认为这样做是因为数据布局中的顺序很重要。它想让它们自然对齐。
  • @linuxfreak:如果你使用 4 字节读取,short 将被读取到与它之前的 char 和填充相同的寄存器中。因此,您可能需要两个额外的指令来删除您不想要的位。通过使用 16 位读取来代替(通常可用),您只会获得所需的 16 位,但必须对齐 16 位读取。
  • @MSalters 当您说 52 字节对齐时,您到底是什么意思?我的意思是它的地址必须是 N 的倍数。
【解决方案2】:

首先,你的前提是错误的。每个对象都以某种基本对齐方式对齐。对于某些标量对象,对齐方式可能与对象的数据大小相同,但也可能更小或更大。例如,一个经典的 32 位体系结构(我在这里考虑 i386)可能包括 8 字节双精度和 10 字节长双精度,两者都具有 4 字节对齐。注意我上面说的是数据大小;不要将此与sizeof 混淆。

对象的实际大小可能大于数据大小,因为对象的大小必须是对象对齐的倍数。原因是无论上下文如何,对象的对齐方式始终相同。换句话说,对象的对齐方式只取决于对象的类型

因此,在结构中:

struct example1 {
  type1 a;
  type2 b;
};

struct example2 {
  type2 b;
  type1 a;
};

两个 b 的对齐方式相同。为了能够保证这种对齐,复合类型的对齐必须是成员类型的最大对齐。这意味着上面的struct example1struct example2 具有相同的对齐方式。

对象的对齐方式仅取决于其类型的要求意味着类型的大小必须是其对齐方式的倍数。 (任何类型都可以是数组的元素类型,包括只有一个元素的数组。数组的大小是元素大小和元素个数的乘积。所以任何必要的填充必须是大小的一部分元素。)

一般来说,在复合类型中重新排列成员可能会改变复合类型的大小,但不能改变复合类型的对齐方式。例如,以下两个结构具有相同的对齐方式——即double 的对齐方式——但第一个几乎肯定更小:

struct compact {
  double d;   // Must be at offset 0
  char   c1;  // Will be at offset sizeof(double)
  char   c2;  // Will be at offset sizeof(double)+sizeof(char).
};

struct bloated {
  char   c1;  // Must be at offset 0
  double d;   // Will be at offset alignof(double)
  char   c2;  // Will be at offset (alignof(double) + sizeof(double))
};

【讨论】:

  • 我认为这不能回答我的问题。我在询问填充字节的硬件级别解释。在 short 的情况下,在 char 和 short 之间添加填充字节。为什么不在短后添加填充字节?这两种情况都只需要读取 1 个内存周期,对吗? (假设处理器可以在 1 个周期内获取 4 个字节)
  • @linuxfreak:在这种情况下可能不需要硬件。但是有软件需求,这就是我要解释的。 C 要求类型的对齐方式是一致的,如果有时 short 必须是 2 字节对齐的(例如,这样它不会在缓存行之间交叉),那么它必须 总是 2 字节对齐。如果您从答案中不明白这一点,请告诉我,我会尽力解决它。
【解决方案3】:

我想我找到了问题的答案。 字节在 char 和 short 之间而不是在 short 之后填充可能有两个原因。

1) 某些架构可能有 2 字节指令,仅从内存中获取 2 个字节。如果是这种情况,则需要 2 个内存读取周期来获取短路。

2) 某些架构可能没有 2 字节指令。即使在这种情况下,处理器也会从内存中获取 4 个字节到寄存器并屏蔽不需要的字节以获得短值。如果字节没有在 char 和 short 之间填充,处理器必须移动字节才能得到 short 值。

以上两种情况都可能导致性能下降。这就是为什么短字节是 2 字节对齐的。

【讨论】:

  • 您忘记了一个重要案例:有些架构甚至不允许允许读取未对齐的值。在这种情况下,编译器必须发出两个内存负载,而不是一加多个位操作指令来获取值,并且它必须在写入时执行类似的操作。后者的问题更大,因为不允许程序发明对相邻数据的写入(由于多线程),因此编译器将不得不退回到逐字节写入值。很明显,语言中最好禁止这些坑的存在。
【解决方案4】:

编译器按照目标处理器(micro-)architectureABI 的规定对齐数据。以x86-64 ABI spec 为例。

如果您的编译器与某些 ABI 指定的对齐方式不同,您将无法从符合该 ABI 的库中调用函数!

在您的示例中,如果(在 x86-64 上)短字段 s 未在 2 个字节上对齐,则处理器必须工作更多(可能发出两次访问)才能获得该字段。

此外,在许多 x86-64 芯片上,高速缓存行通常是 16 字节(或者可能更少)的倍数。因此,将 call stack 帧对齐 16 个字节是有意义的。这对于类向量局部变量(AVX、SSE3 等)是必需的

在某些处理器上,错误对齐的数据会导致错误(例如,interrupt 表示机器异常)或显着减慢处理速度。此外,它可以呈现一些非原子访问(用于多核处理)。因此,一些 ABI 规定了比严格必要的更多的 ABI。此外,CPU 的一些最新功能(例如 vectorization,例如通过 SIMD 指令,例如 AVXSSE3)确实受益于非常对齐的数据(例如对齐到 16 个字节)。如果你的编译器知道这种强对齐,它可能会进行更多优化——使用这样的指令。

【讨论】:

  • 在所有基于 Core2 的处理器上,高速缓存行是 64 字节而不是 16 字节。对于 64 位代码,调用堆栈与 16 字节对齐。对于 32 位代码,它不一定与 16 字节对齐。
  • 对不起...我错过了“16 字节的倍数”中的 MULTIPLE 这个词。对于 core2,倍数是 4。也许在 Pentium4 上是 2(32 字节)?它在某个时候是否只有 16 个字节(例如在 Pentium4 之前)?这是调用堆栈是 16 字节的倍数的真正原因吗?
猜你喜欢
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
  • 2011-01-23
  • 2017-12-21
  • 2012-04-30
  • 2023-03-25
  • 2013-05-18
  • 2013-01-31
相关资源
最近更新 更多