9

根据这篇文章的讨论,我了解到结构成员对齐的主要原因是性能(以及一些架构限制)。

如果我们在为 32 位 x86 编译时研究 Microsoft (Visual C++)、Borland/CodeGear (C++-Builder)、Digital Mars (DMC) 和 GNU (GCC):对齐为int4 个字节,如果int未对齐,则可能会读取 2 行内存库。

我的问题是为什么不使double4 个字节也对齐?对齐的 4 个字节double也会导致读取 2 行内存库....

例如在下面的示例中,由于double是 8 对齐的,因此结构的实际大小将为sizeof(char) + (alignment for double padding) + sizeof(int) = 20 bytes.

typedef struct structc_tag{
    char        c;
    double      d;
    int         s;
} structc_t;

谢谢

4

4 回答 4

12

扩展评论:

根据关于 GCC 的文档-malign-double

double在两个字边界上对齐变量会产生在奔腾上运行得更快的代码,但会消耗更多内存。

在 x86-64 上,-malign-double默认启用。

警告:如果您使用-malign-double开关,包含上述类型的结构与已发布的 386 应用程序二进制接口规范的对齐方式不同,并且与未使用该开关编译的代码中的结构不兼容。

这里的一个字表示 i386 字,即 32 位。

即使在 32 位模式下, Windows 也使用 64 位对齐double值,而符合 SysV i386 ABI 的 Unices 使用 32 位对齐。32 位 Windows API Win32 来自 Windows NT 3.1,与当前的 Windows 版本不同,它针对 Intel i386、Alpha、MIPS 甚至是不起眼的 Intel i860。由于像 Alpha 和 MIPS 这样的原生 RISC 系统要求double值是 64 位对齐的(否则会发生硬件故障),可移植性可能是 Win32 i386 ABI 中 64 位对齐的基本原理。

64 位 x86 系统,也称为 AMD64 或 x86-64 或 x64,要求double值是 64 位对齐的,否则会发生未对齐错误,并且硬件会进行昂贵的“修复”,这会大大减慢内存访问速度。这就是为什么double在所有现代 x86-64 ABI(SysV 和 Win32)中值都是 64 位对齐的。

于 2012-06-19T22:15:24.467 回答
6

大多数编译器会自动将数据值与平台的字长或数据类型的大小对齐,以较小者为准。绝大多数消费者和企业处理器使用 32 位字长。(即使是 64 位系统通常也使用 32 位作为本机字长)

因此,结构中成员的排序可能会浪费一些内存。在你的具体情况下,你很好。我将在评论中添加已用内存的实际占用空间:

typedef struct structc_tag{
          char        c; // 1 byte
                         // 3 bytes (padding)
          double      d; // 8 bytes
          int         s; // 4 bytes
} structc_t;             // total: 16 bytes

此规则也适用于结构,因此即使您重新排列它们以使最小的字段位于最后,您仍将拥有相同大小(16 字节)的结构。

typedef struct structc_tag{
          double      d; // 8 bytes
          int         s; // 4 bytes
          char        c; // 1 byte
                         // 3 bytes (padding)
} structc_t;             // total: 16 bytes

如果您要声明更多小于 4 字节的字段,如果按大小将它们分组在一起,您可能会看到一些内存减少。例如:

typedef struct structc_tag{
          double      d1; // 8 bytes
          double      d2; // 8 bytes
          double      d3; // 8 bytes
          int         s1; // 4 bytes
          int         s2; // 4 bytes
          int         s3; // 4 bytes
          short       s4; // 2 bytes
          short       s5; // 2 bytes
          short       s6; // 2 bytes
          char        c1; // 1 byte
          char        c2; // 1 byte
          char        c3; // 1 byte
                          // 3 bytes (padding)
} structc_t;              // total: 48 bytes

声明一个愚蠢的结构可能会浪费大量内存,除非编译器重新排序您的元素(通常不会,除非被明确告知)

typedef struct structc_tag{
          int         s1; // 4 bytes
          char        c1; // 1 byte
                          // 3 bytes (padding)
          int         s2; // 4 bytes
          char        c2; // 1 byte
                          // 3 bytes (padding)
          int         s3; // 4 bytes
          char        c3; // 1 byte
                          // 3 bytes (padding)
} structc_t;              // total: 24 bytes 
                          // (9 bytes wasted, or 38%)
                          // (optimal size: 16 bytes (1 byte wasted))

双精度大于 32 位,因此根据第一节中的规则,是 32 位对齐的。有人提到了一个改变对齐方式的编译器选项,并且默认编译器选项在 32 位和 64 位系统之间是不同的,这也是有效的。所以关于双打的真正答案是它取决于平台和编译器。

内存性能由单词控制:从内存加载取决于数据的位置。如果数据包含一个字(即字对齐),则只需加载该字。如果它没有正确对齐(即 0x2 处的 int),处理器必须加载 2 个字才能正确读取其值。这同样适用于双精度,通常占用 2 个字,但如果未对齐,则占用 3。在 64 位系统上,可以本地加载 64 位数量,它们的行为类似于 32 位系统上的 32 位整数,如果正确对齐,它们可以一次加载,但否则,它们将需要 2 个。

于 2012-06-19T20:16:21.603 回答
4

首先是架构强加了对齐要求,有些会容忍未对齐的内存访问,有些则不会。

让我们以 x86-32bitwindows平台为例,在这个平台上分别对和intlong对齐要求。4 bytes8 bytes

很清楚为什么int对齐要求是4 bytes,只是为了让 cpu 只能通过一次访问来读取它。

doulbeis8 bytes和 not的对齐要求的原因是4 bytes,如果是,4 bytes那么考虑如果这个 double 位于该地址60并且高速缓存行大小是会发生什么64bits,在这种情况下,处理器需要从内存中加载 2 个高速缓存行。缓存,但如果这double是对齐的,则不会发生这种情况,因为在这种情况下,double它将始终是一个缓存行的一部分,而不是在两个缓存行之间划分。

   ...58 59|60 61 62 63    64 65 66 67|68 69 70 71...
     -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
 ----------+  +  +  +  .  .  +  +  +  +--------------
           |           .  .           |
 ----------+  +  +  +  .  .  +  +  +  +-------------- 
                       .  .         
      Cache Line 1     .  .  Cache Line 2
     -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
于 2014-03-30T16:42:51.837 回答
0

就 CPU 架构而言,这个问题是高度特定于平台的。例如,在对非 4 字节对齐地址进行操作的体系结构中,将变量(实际上是它们的地址)对齐到 4 字节可以避免这种惩罚。

编译器非常适合这类东西,尤其是当您为他们提供要优化的目标 CPU 架构时,他们可以为您完成大部分工作,以及许多其他优化。以 GCC 的-marchflag 为例,它可以让您以 CPU 架构为目标。

于 2012-06-19T19:57:45.420 回答