为什么结构体的对齐不等于它的大小?

Huy*_*ran 1 c c++ memory struct memory-alignment

我读过一些有关内存对齐的问题,但没有找到与我的问题相关的任何答案。

这里我有2个问题:

struct MyData
{
    short Data1;
    short Data2;
    short Data3;
};
Run Code Online (Sandbox Code Playgroud)
  1. 为什么对齐方式是MyData等于2而不是6 == sizeof(MyData)?是否有任何特定原因导致struct使用与其他基元类型不同的方法来计算对齐方式?

  2. 根据微软的Alignment文档:

    如果地址的对齐方式为 Xn+0,则称该地址与 X 对齐

    这是否意味着只有当对象的MyData地址是其对齐方式的倍数时才对齐(2在本例中就是这样)?如果是这样,那么我们可以在内存中分配的对齐对象越大struct,对齐的对象就越少吗?MyData我的假设正确吗?

Eri*_*hil 7

对齐要求源于计算机硬件的工作方式,特别是内存总线。

\n

典型的现代处理器不会一次从内存加载一个字节。它通过总线连接到内存,总线本质上是一组将数据从一个地方传送到另一个地方的电线。使用 64 根线,一次可以传输 64 位(8 个字节)。为了便于说明,本答案将使用具有 64 位总线的系统。这是一个简化的抽象视图;物理总线可能具有用于控制的附加线路,并且可能具有关于传输数据的复杂协议。此外,为了简单起见,没有考虑内存缓存。

\n

由于总线可以一次传输八个字节,因此存储器被组织成八个字节的组。每个组都有一个编号,因此有组 0、组 1、组 2,依此类推。当处理器想要从内存加载数据时,它通过总线将组号发送到内存,内存通过发送该组的内容进行响应。

\n

从计算机上运行的程序来看,内存中的每个字节都有自己的地址:0,1,2,3,4,5,6,7,8,9,10,11,12,13,\xe2 \x80\xa6 当程序请求地址 12,345 处的字节时,处理器没有任何物理方法可以仅从内存中请求该字节。相反,它计算组号。将字节组织为 8 个组时,字节 12,345 位于组 Floor(12,345 / 8) = 1543 中,字节 12,345 是该组的字节 1。因此,处理器要求内存发送组 1543 的内容。当处理器获取这八个字节时,它会从该组中取出字节号 1 并将其提供给您的程序。

\n

接下来,假设您有一个 16 位的short int. 如果编译器为其分配起始位置 12,344,因此它位于字节 12,344 和 12,345 中,那么当处理器需要加载它时,它会加载组 1543 并从该组中获取字节 0 和 1。这工作正常。另一方面,如果编译器分配起始位置 12,343,则 your 的第一个字节short int将是组 1542 中的字节 7,第二个字节将是组 1543 中的字节 0。要加载 your short int,处理器必须要求内存发送组 1542,它还必须要求内存发送组 1543,然后它必须从第一组中取出字节 7,从第二组中取出字节 0。现在,程序中的一条加载指令需要两次内存操作。

\n

有些处理器无法处理这个问题;它们的设计目的不是将单个加载指令拆分为多个内存操作。如果它们收到此类负载的请求,则会触发程序异常。有些处理器的设计使得这样的负载是不可能的;用于加载地址的位甚至不包含位置值为 1、2 或 4 的位(取决于正在加载的对象的宽度)。仅使用高位。这些位包含组号,并且低位假定为零。其他处理器的设计使得它们可以将加载指令拆分为多个内存操作,但这很慢。在所有这些情况下,我们希望 C 程序对齐其对象,这样就不会出现此问题。

\n

制定每个对象必须根据其对齐要求进行对齐的规则可以解决此问题。如果两字节short int必须始终从两个字节的倍数地址开始,则它永远不能分割到两个内存组中。存储器组1543可以容纳四个short int,一个在字节12,344和12,345,另一个在字节12,346和12,347,另一个在字节12,348和12,349,最后一个在字节12,350和12,351。

\n

请注意,您可以将 a 放在short int12,345 和 12,346 处,并且它会位在一个内存组中。然而,我们不能创建一个很长的数组short int,因为下一个必须位于 12,347 和 12,348,然后下一个位于 12,349 和 12,350,下一个位于 12,351 和 12,352\xe2\x80\x94,最后一个是分为两个内存组:1543 和 1544。因此,即使short int可以将单个内存放在某些奇数地址处,我们也制定一条规则,即每个内存组都short int必须从两个字节的倍数地址开始。

\n

类似地,我们要求每个四字节int必须以四字节的倍数开始,因为这保证了每个四字节都int在一个内存组中,并且对于它们的数组来说也是如此。

\n

假设我们有一个 16 字节的原始对象,可能是一个int128_t. 在这个系统中,我们只需要八字节对齐。要了解原因,请考虑该对象始终需要至少两个内存组,因为它比一个内存组大。因此,即使不考虑对齐,也需要两个内存组。如果它从一个内存组(八个字节)开始,那么它正好占用两个内存组。如果它从其他地方开始,那么它会占用一个内存组中的几个字节,然后占用另一组的所有八个字节,然后占用第三组中的几个字节。因此,如果该对象没有八字节对齐,则它会占用比理想需要更多的内存组。因此,对于 16 字节对象,要求其具有 8 字节对齐对于效率来说是必要的,并且已经足够了。要求它具有更严格的对齐方式(16 字节的倍数)不会减少加载它所需的内存组的数量。

\n

现在考虑你的结构。它包含三个short int. 要求结构进行两字节对齐足以保证该结构的每个成员也具有两字节对齐。这意味着两字节对齐要求足以保证该结构在计算机硬件\xe2\x80\x94上工作,处理器永远不会被要求加载short int跨两个内存组的成员。

\n

加载单个成员并不是我们对结构所做的唯一事情。有时我们将一个结构复制到另一个结构,因此我们加载整个结构并将其存储在其他地方。如果该结构只有双字节对齐,则编译器可能会为其分配起始位置 12,350,因此第一个成员是组 1543 中的字节 7 和 8,第二个成员是组 1544 中的字节 0 和 1,并且第三个成员是该组中的字节 2 和 3。因此加载结构需要加载两个内存组。如果我们要求该结构具有八字节对齐,则编译器无法在 12,350 处启动它;它必须从内存组的开头开始,并且始终可以通过单个内存操作加载整个结构。

\n

在某些情况下这是合理的做法,您可以使用 C\xe2\x80\x99s_Alignas功能来请求它。然而,它使用了更多的空间,因为这个六字节结构需要两个字节的填充。尚未发现以这种方式自动增加结构对齐要求通常是有用的,因此通常不这样做。(如果实现者选择的话,C 标准允许 C 实现执行此操作。)

\n

由于地址计算的性质,普通计算机硬件中的对齐要求是 2 的幂:我们使用位来生成二进制数字来表示地址,因此将内存组织为八个一组很方便。为了计算组号,我们除以八,这很容易,因为它只涉及使用地址的高位。如果我们使用六个字节的组,则必须除以六,这需要算术运算;它不能通过简单的位移来完成。理论上可以设计出使用六个字节组进行内存组织的计算机硬件,并且将六个字节作为许多对象的对齐要求,但由于需要额外的工作来支持,该硬件的效率将低于我们当前的硬件。六个字节的存储器组。

\n