我正在做一些示例程序来探索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;
}
编译时
warning: alignment must be a small power of two, not 3 [-Wpragmas]
warning: #pragma pack (pop) encountered without matching #pragma pack (push) [-Wpragmas]
看起来#pragma不起作用.输出只有24.即4字节对齐.
5条答案
按热度按时间f1tvaqid1#
简短的回答是,处理器中的基本对象的大小是2的小幂(例如,1、2、4、8和16字节),而内存是按大小是2的小幂(例如,8字节)的组来组织的,因此结构必须对齐才能与这些大小很好地工作。
长时间的回答是,其原因是基于物理学和初等数学,计算机自然会处理值为0和1的比特,这是因为设计在两个值之间切换的物理事物很容易:高电压和低电压,电荷的存在或不存在,等等。区分三个值更难,因为你必须对值之间的转换更敏感。所以,随着计算机技术在过去几十年中的发展,我们已经使用位(二进制数字)来代替像三进制数字这样的替代物。
为了得到更大的数,我们把多个位组合起来。所以两个位组合起来可以有四个值。三个位可以有八个值,以此类推。在较老的计算机中,有时一次把六位或十位组合在一起。然而,八位变得很常见,现在基本上是标准的。一个字节使用八位并不像我描述的其他一些组合那样有强烈的物理原因。但这就是这个世界。
计算机的另一个特点是内存。一旦我们有了这些字节,我们就想把它们存储在一个处理器容易访问的设备中,这样我们就可以快速地把大量的字节送入和送出处理器。当我们有大量的字节时,我们需要一种方法让处理器告诉内存它想读或写哪些字节。所以处理器需要一种方法来寻址字节。
处理器使用位表示值,所以它将使用位来表示地址值。2所以存储器将被构造成接受位来指示当处理器读取时哪些字节被提供给处理器,或者当处理器写入时哪些字节被存储。3存储器设备用这些位做什么呢?4一件容易的事情是使用一位来控制到存储器的路径的一个开关。内存将由许多存储字节的小部分组成。
假设存储设备中有一个可以存储一个字节的东西,其中两个东西相邻,比如A和B。我们可以使用开关来选择是A字节有效还是B字节有效。现在考虑其中四个东西,比如A、B、C。和D。我们可以使用一个开关来选择是使用A-B组还是使用C-D组。然后另一个开关选择A或B(如果使用A-B组)或C或D(如果使用C-D组)。
这一进程将继续:存储器地址中的每一位选择一组存储单元以供使用。1位在2个存储单元之间选择,2位在4个存储单元之间选择,3位在8个存储单元之间选择,4位在16个存储单元之间选择,依此类推。8位在256个存储单元之间选择,24位在16,777,216个存储单元之间选择,且32位在4,294,967,296个存储单元之间选择。
还有一个复杂的问题。在处理器和内存之间移动单个字节是很慢的。相反,现代计算机把内存组织成更大的块,例如8个字节。你一次只能在内存和处理器之间移动8个字节。当处理器请求内存提供一些数据时,处理器只发送地址的高位--低位的3位选择8个字节中的单个字节。并且它们不被发送到存储器。
这样做的速度更快,因为处理器只需要8个字节,而存储器只需要1个字节就可以完成所有的转换。这样做的成本更低,因为你不需要大量的额外转换来区分存储器中的各个字节。
然而,现在这意味着处理器无法从内存中获取单个字节。当你执行一条访问单个字节的指令时,处理器必须从内存中读取8个字节,然后在处理器内部移动这些字节以获取你想要的字节。类似地,要获取2或4个字节,处理器读取8个字节并提取你想要的字节。
为了简化这个过程,处理器设计者指定数据应该以特定的方式对齐。通常,他们要求2字节数据(如16位整数)对齐到2字节的倍数,4字节数据(如32位整数和32位浮点值)对齐到4字节的倍数,8字节数据对齐到8字节的倍数。
这种必需的对齐方式有两个作用:首先,在从内存读取的一个八字节块中,四字节数据只能从两个位置开始(开头或中间),处理器设计者仅需要放置导线以从两个位置提取四个字节。他们不需要添加所有额外的连线来从八个单独字节中的任何一个中提取四个字节,这些字节可以是起始位置(如果对齐(一些处理器将完全禁止加载未对齐的数据,而一些处理器将允许加载未对齐的数据,但使用较慢的方法来提取它,所述方法使用较少的线,但使用迭代算法来在若干处理器周期上移位数据,因此未对齐的加载较慢)。
第二个影响是,由于4字节数据只能在8字节块中的两个位置开始,因此它也在该块中结束。考虑一下,如果您试图加载从8字节块的第六个字节开始的4字节数据,会发生什么情况。前两个字节在块中,但接下来的两个字节在内存中的下一个块中。处理器将不得不从内存中读取两个块。从每一个数据块中取出不同的字节,然后把这些字节放在一起。2这比只阅读一个数据块要慢得多。
所以,内存是按照2的幂来组织的,因为这是位的自然结果,处理器需要对齐,因为这使得内存访问更高效,对齐自然是2的幂,这就是为什么当结构大小是用于对齐的2的幂的倍数时,结构会更好地工作。
fykwrbwg2#
在结构中添加填充是因为CPU在对齐的数据上工作得更快(在某些架构上,它们根本不处理未对齐的数据),而且各种数据类型的对齐要求总是2的小幂(至少在我听说过的任何架构上)。
尽管如此,如果出于某种奇怪的原因,您确实需要任意对齐,没有什么可以阻止您在正确的位置添加伪
char
数组来强制您的对齐(这或多或少是编译器在幕后所做的事情)。tvz2xvvm3#
内存总线只有这么多字节宽,通常是2字节宽的幂,因为这是字段中位的最大有效使用
这样的三位字段
可能有八个数字的表示
如果你把自己局限在数字上
那么你就浪费了最高阶的比特,它永远是零。在计算的早期,硬件和软件设计师需要他们能抓住的每一个比特,因为内存是“非常昂贵的”,所以这种浪费被设计出了系统。
后来,当存储器子系统增长时,对齐存取变得设计起来更便宜。对齐存取要求数据元素的开始位于某些边界上,以减少通过存储器总线的传输次数,并减少总线管理中的计算次数。
“2的幂”要求是总线体系结构的副作用,再加上一个简单的例程,确保C数据结构可以与对齐的访问边界对齐。
knpiaxh14#
虽然计算机设计倾向于内存2的幂内存对齐有物理原因,但另一个同样重要的要求是所有内存对齐必须均匀地划分为某个数字。否则,例如,如果一个结构体需要在7的倍数上对齐,一个需要在8的倍数上对齐,一个需要在9的倍数上对齐,包含所有三个结构体的
union
必须与504的倍数对齐。处理可分性要求的通常方式是说所有对齐大小必须细分2的某个大的幂。这样的方法可以工作,但它不是唯一可行的实现。如果有人愿意,可以设计与120字节高速缓存行一起工作的硬件,并允许对象在2、3、4、5、6、8的倍数上对齐。10、12、15、20、24、30、40、60或120字节,这样的设计从硬件的Angular 来看其实是非常合理的(每个高速缓存行将被存储为1024位,包括64位纠错、写标记或其它信息)并且将允许80位实数的有效存储,RGB或XYZ三色组(包括80位实数的任何数字类型)。如果不要求物理地址与逻辑地址具有相同的数字序列,那么将120字节范围Map到高速缓存线所需的电路将不会过于昂贵。另一方面,在专用应用之外,非2的幂Map的好处不太可能足以克服成本和市场惯性的问题。
t5zmwmid5#
根据GCC文档(http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html),压缩杂注用于“与Microsoft Windows编译器兼容”。
如果您搜索MS文档中关于对齐(http://msdn.microsoft.com/en-us/library/ms253949(v=vs.80).aspx)的内容,您会发现MSVC编译器确保了基于数据大小的类型对齐;正如其他帖子所解释的那样,
logically
总是2的幂。如果您的问题是需要对数据结构进行一些花哨的对齐(以访问一些意识不清的内存Map外围设备),那么最好的办法是通过添加添加所需填充的字段来使用
manual
结构打包。