首页 > 解决方案 > 为什么结构在 64 位机器上与 4 字节(32 位)对齐?

问题描述

我试图用这段代码理解一些关于结构填充的事情:

#include <stdio.h>
#include <stdint.h>

struct azaza { // of course suboptimal arrangement of elements
     uint32_t addr1;
     uint32_t addr2;
     uint8_t tmp;
     uint32_t addr3;
     uint8_t flags;
};

int main(void) {
     printf("%d\n", sizeof(struct azaza));

     return 0;
}

输出是:20
但我期望的是24,因为我的机器和我的操作系统是 64 位的,我认为对齐应该在 4 字节边界上。为什么 x86-64 操作系统上的结构对齐在 4 字节边界上?

标签: cdata-structuresstructsizeofmemory-alignment

解决方案


术语“64 位机器”是模糊的。计算机处理器和系统具有多个功能,在同一台机器上可能具有不同的大小,包括:

  • 大多数处理器寄存器的宽度。
  • 地址的宽度。
  • 数据总线的宽度。
  • 算术逻辑单元的宽度。

目前,让我们假设所有这些都是 64 位。即便如此,为什么我们会要求uint32_t对齐到 64 位?

需要对齐的一个原因是避免在内存传输之间拆分访问。如果总线为 64 位宽,则系统通常设计为以 8 字节(64 位)的倍数访问内存。当处理器想要读取一些内存时,比如从 64 位地址读取,它只将前 61 位发送到内存设备。(61 很多,但我们假设这台机器中的所有内容都是 64 位。)存储设备获得与这 61 位匹配的所有八个字节——我们没有发送的低三位的所有八个组合。它一次获取 8 个字节,因为这适合总线,而且我们希望提高效率。

因此,每当进程从内存中读取数据时,它总是会获得 8 个字节,并且这些字节将是 64 位对齐的。

现在我们可以看到,如果 auint32_t从某个地址开始,比如 xxx0101,其中 x 代表我们不关心的位,它的四个字节将位于地址 xxx0101、xxx0110、xxx0111 和 xxx1000。但是第四个字节在不同的八组中。前三个都在同一个组中,一个由初始位 xxx0 寻址。最后一个字节在一个新组 xxx1 中。要读取这个uint32_t,我们必须从内存中读取两次。那是低效的。

但是,如果uint32_t它位于地址 xxx0000 或 xxx1000,则其字节都在一个组中。它们可能是该组中的前四个或后四个字节,因此我们需要处理器能够从它从内存中获取的八个字节中选择前四个或后四个字节,但它只需要从内存中读取一次获取字节。

因此,四字节对齐uint32_t足以确保它足够好对齐,我们只需要一次读取就可以从内存中获取它。

几乎没有理由要求八字节对齐。一个原因可能是,如果它是八字节对齐的,我们就不需要处理器中的额外电线和开关来选择前四个字节或八个字节中的后四个字节。我们只需要拿前四个。但是这个微小的优势被它意味着我们只能存储uint32_t每 8 个字节中的一个的事实大大压倒了。一半的内存会被填充浪费掉。通过四字节对齐,我们可以uint32_t很好地读取对象,一次可以读取两个。

使用uint8_t8 字节对齐会更糟,我们可能uint8_t每 8 个字节中只有一个,浪费了 87.5% 的内存。

在大多数情况下,长度为n字节的对象只需要进行n字节对齐即可在硬件上运行良好(假设n是 2 的幂)。这种对齐将使它们能够巧妙地适应总线和内存操作,无论它们的宽度是多少。

此外,如果总线宽度为b且对象大小为n,则对齐要求可能只是bn中的较小者。一旦一个对象大于总线宽度,我们将需要多次传输来获得它,并且通常需要比总线宽度更多的对齐来获得它。


推荐阅读