为什么填充必须是2的幂?

Jey*_*ram 3 c struct

我正在做一些示例程序来探索C,并想知道为什么结构填充只能以2的幂来完成.

#include <stdio.h>

#pragma pack(push, 3)

union aaaa
{

   struct bbb
   {
      int a;
      double b;
      char c;
   }xx;

   float f;
};

#pragma pack(pop)

int main()
{

printf("\n Size: %d", sizeof(union aaaa));

return 0;
}
Run Code Online (Sandbox Code Playgroud)

在编译时

warning: alignment must be a small power of two, not 3 [-Wpragmas]
warning: #pragma pack (pop) encountered without matching #pragma pack (push) [-Wpragmas]
Run Code Online (Sandbox Code Playgroud)

似乎#pragma没有效果.输出仅为24.即4字节对齐.

Eri*_*hil 17

简短的回答是处理器中的基本对象具有小的2的幂(例如,1,2,4,8和16字节),并且存储器被组织成大小为2的小功率的组(例如,8因此,必须对齐结构才能与这些尺寸一起使用.

长期的答案是,其原因是基于物理学和初等数学.计算机自然使用位,值为0和1.这是因为很容易设计在两个值之间切换的物理事物:高电压和低电压,存在充电或没有电荷等等.区分三个值更难,因为您必须对值之间的转换更敏感.因此,随着计算机技术在几十年的发展,我们使用了比特(二进制数字)而不是像三位数这样的替代品.

为了得到更大的数字,我们组合了多个位.因此,两个位可以组合成四个值.三位可以有八个值,依此类推.在较旧的计算机中,有时一次是第六组或第十组.然而,八个变得普遍,现在基本上是标准的.对一个字节使用八位并不像我描述的其他一些分组那样具有强大的物理原因,但它是世界的方式.

计算机的另一个特性是内存.一旦我们拥有这些字节,我们就希望将它们存储在一个易于处理器访问的设备中,这样我们就可以快速地将大量字节输入和输出处理器.当我们有很多字节时,我们需要一种方法让处理器告诉内存处理器想要读或写的字节.因此处理器需要一种方法来处理字节.

处理器使用位作为值,因此它将使用位作为地址值.因此,存储器将被构建为接受位以指示当处理器读取时要向处理器提供哪些字节或者在处理器写入时存储哪些字节.存储器设备对这些位做了什么?一个简单的方法是使用一位来控制通向存储器的路径的一个开关.内存将由许多存储字节的小部分组成.

考虑一下存储设备中可以存储一个字节的东西,并考虑其中两个相邻的东西,比如A和B.我们可以使用一个开关来选择我们是想要A字节是活动还是B字节到积极点.现在考虑其中的四个,比如A,B,C和D.我们可以使用一个开关来选择是使用AB组还是使用CD组.然后另一个开关选择A或B(如果使用AB组)或C或D(如果使用CD)组.

此过程继续:内存地址中的每个位选择要使用的一组存储单元.1位选择2个存储单元,2个选择4个,3个选择8个,4个选择16个,依此类推.8位选择256个存储单元,24位选择16,777,216个存储单元,32位选择4,294,967,296个存储单元.

还有一个并发症.在处理器和内存之间移动单个字节很慢.相反,现代计算机将内存组织成更大的部分,例如八个字节.您只能在内存和处理器之间一次移动八个字节.当处理器请求存储器提供一些数据时,处理器仅发送地址的高位 - 低三位选择八个字节内的单个字节,并且它们不被发送到存储器.

这是更快的,因为处理器在内存完成所有切换以提供一个字节所需的时间内获得8个字节,并且它更便宜,因为您不需要大量额外的开关来区分个体内存中的字节数.

但是,现在它意味着处理器无法从内存中获取单个字节.当您执行访问单个字节的指令时,处理器必须从内存中读取8个字节,然后在处理器内部移动这些字节以获得所需的一个字节.类似地,为了获得两个或四个字节,处理器读取八个字节并仅提取所需的字节.

为简化此过程,处理器设计人员指定数据应以某种方式对齐.典型地,它们需要两个字节数据(如16位整数),以对齐的两个字节的倍数,4字节的数据(如32位整数和32位浮点值)对齐到四的倍数字节和八字节数据对齐到八个字节的倍数.

这种必需的对齐有两个影响.首先,因为四字节数据只能从从存储器(开始或中间)读取的8字节块中的两个位置开始,所以处理器设计者只需要插入电线以从两个位置提取四个字节.如果允许任何对齐,它们不需要添加所有额外的线来从八个单独字节中的任何一个中提取四个字节,这些字节可能是起始位置.(一些处理器将完全禁止未对齐的加载数据,并且一些处理器将允许它,但使用慢速方法提取它使用较少的线材,但使用迭代算法应用于数据在几个处理器周期移位,所以未对齐的加载是缓慢的.)

第二个影响是,因为四字节数据只能从八字节块中的两个位置开始,所以它也会在该块内部结束.考虑如果您尝试加载从8字节块的第六个字节开始的四个字节的数据会发生什么.前两个字节在块中,但接下来的两个字节在内存中的下一个块中.处理器必须从内存中读取两个块,从每个块中取出不同的字节,然后将这些字节放在一起.这比读取一个块慢得多.

因此,存储器由2的幂组织,因为这是位的自然结果,并且处理器需要对齐,因为这使得存储器访问更有效.对齐自然是2的幂,这就是为什么当它们是用于对齐的2的幂的倍数时,你的结构尺寸更好.

  • 添加到上面:即使内存以其他单位(例如 6 字节)组织,也很难处理该单位的倍数对齐。判断一个地址是否是2的幂的倍数很容易:测试低位是否为零。很难确定一个地址是否是六的倍数:您必须计算余数。处理器具有用于此目的的算术单元,但低级硬件寻址机制要简单得多并且无法执行此算术。因此,制造为 6 组找到合适内存单元的硬件将非常昂贵。 (2认同)

Mat*_*lia 8

因为否则就没有意义了.您可以为结构添加填充,因为CPU在对齐数据上工作得更快(在某些体系结构上,它们根本不能在未对齐的数据上工作),并且各种数据类型的对齐必需条件总是小于2的幂(至少,在我听说过的任何架构上).

但是,如果出于某种奇怪的原因,你确实需要任意对齐,那么没有什么可以阻止你char在正确的位置添加虚拟数组来强制你的对齐(这或多或少是编译器在引擎盖下做的事情).

  • 告诉我怎么回事儿.直到我发现m86k由于未对准而随机"分裂",我才得到了我的第一个白发. (2认同)