到底为什么要进行字节对齐?该怎么处理

到底为什么要进行字节对齐?
最近一段时间总是被内存对齐这个问题困扰着,上网查找了一些资料,可以说这些资料讲解的都是一个模板,就是为了提高cpu的访问效率,如果对齐的话需要一个时钟周期访问,那么不对齐的话就需要两个时钟周期访问,所以要进行字节对齐,说一句不太好听的话,全是废话,谁不知道是为了提高访问效率,还用他们说,都没有讲解出到底为什么要进行内存对齐(不需要告诉我这是为了提高cpu的效率,呵呵!),cpu是如何通过数据总线来读取内存中的数据?

这个是我个人的理解(不知道对不对):

内存访问粒度是不变的,现在的计算机中cpu的内存访问粒度是四个字节,也就是每次从内存中总是取四个字节的数据,如果不存在char与short类型的数据的话cpu每次都会取得想要的值,不用进行一个剔除就能够得到想要的,可是事实上是存在char类型与short类型的数据,这样cpu在取这两种类型的数据的时候就需要进行剔除了,因此在效率上就存在了一个损失,但是永远都不可能实现“利益”的最大化,虽然在效率上存在一定的损失,可是却节约了一部分的内存空间,字节对齐只是一个折中的方法,这种方法并不是十全十美的!!


计算机都有一个默认的对齐模数,如果说默认对齐的模数是四个字节的话,那么是不是每个类型的数据都会对齐到四个字节呢??比如说一个char类型的数据a和一个short类型的数据1 ,char类型数据起始地址是0x00的话,那么按照默认的四字节对齐,应该是这样的吗?

0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08

0a cc cc cc 01 cc cc cc cc

后面的字节是为了四字节对齐而填充的呢?

不知道我的理解是否是对的!望高手指正!!

 

但是如果我们设置了对齐字节数的话,比如我们设置了#pragma pack(1),这样计算机中默认的对齐字节数就不再发挥作用了??

这样对齐的字节数就会按照有效对齐字节数来进行字节对齐呢?

如:

0x00 0x01 0x02  

0a 01 cc

我的这个理解是对的吗??望指正!!

 

如果我的理解没有问题的话,那么为什么还要设置字节对齐呢??这样不是在cpu访问效率上是一种损失吗如果是提高cpu的访问效率的话,因为数据总线的宽度是32位的,那为什么不把每一个类型的数据都设置为四个字节对齐呢???难道就是为了节约内存空间而损失了访问效率?

希望高手能够给出一个全面的解释呀!呵呵!



------解决方案--------------------
CPU 的访问粒度不仅仅是大小限制,地址上也有限制。也就是说,CPU 只能访问对齐地址上的固定长度的数据。
以四字节对齐为例,就是只能访问 0x0 - 0x3,0x4 - 0x7, 0x8 - 0xc 这样的(闭)区间,不能跨区间访问。
如果真正需要访问的数据并没有占据那个区间的全部字节范围,还有另外的信号线来指出具体操作哪几个字节,类似于掩码的作用。好像也有些架构干脆就不允许这种部分访问,强制要求按粒度访问。

如果一个数据跨越了两个这样的区间,那么就只能将这个数据的操作拆分成两部分,分别执行,效率当然就低了。

解决这个问题的一个办法就是强制数据对齐,现在假设一个 16 字节对齐的系统(稍微新一点的 x86 架构应该都是 16 字节对齐):
显然,一个字节无论如何不会跨越两个对齐区间;
当一个 2 字节的数据放在奇数地址上,就有可能跨越两个区间,但放在偶数地址上(即 2 字节对齐)就肯定不会;
同样,只要 4 字节数据放在 4 字节地址上,8 字节数据放在 8 字节地址上,一定不会跨越两个区间。

这就是比较常见的字节对齐策略,将长度小于一个对齐单位的数据对齐到不小于它的长度的首个二的幂次。(晕,这句话好绕……)
------解决方案--------------------
假定你工作在32位机上,
假定你分配的变量的起始地址为0x00000001H,是一个占四字节空间的整形量。
这个地址就不是按照四字节地址对齐的,处理器在访问这个变量是,由于处理器的数据总线位宽是32位,所以处理器必须进行两次内存访问操作,第一次从地址0x00000000h-0x00000003读入一次,第二次从0x00000004-0x00000007读入一次,然后再将0x00000001H-0x00000003和0x00000004H的内容进行组合,才得到了你想要获得的数据,这样会造成性能的下降。

当你使用编译器开关进行4字节对齐时,只需要一次访内操作,就可以读入一个整型数据。
------解决方案--------------------
去看下计算机组成原理你就知道的太多了
------解决方案--------------------
为啥要对齐,这个要从总线的硬件实现上来讲了
而你在编代码的时候写的#pragma pack(1),并不会改变CPU访问总线的方式
#pragma pack(1)只是让编译器不再对齐,而没有让CPU访问内存不对齐
这硬件对齐而软件不对齐的差异要靠CPU花时间来解决。
------解决方案--------------------

------解决方案--------------------
一次访问时,要么读0x01~0x04,要么读0x05~0x08……
硬件不支持一次访问就读到0x02~0x05
举个例子,如果0x02~0x05存了一个int,读取这个int就需要先读0x01~0x04,留下0x02~0x04的内容,再读0x05~0x08,留下0x05的内容,两部分拼接起来才能得到那个int的值……
读一个int就要两次内存访问,效率就低了……



------解决方案--------------------
我的理解是:

CPU在取数据时使用缓存,缓存每次缓冲固定大小的数据(以4Byte为例),而且是在特定的内存地址上预取数据(在这里是4的倍数);故如果把一个4字节的变量放在非4的倍数的内存地址上,则需要两次预读取才可完成变量访问,便降低了访问速率。
这也是为什么结构体变量定义建议把大变量放在前面的原因吧。
------解决方案--------------------
#pragma pack(n)
只是告诉编译器所需要使用的对齐方式,然后编译器会按要求生成目标代码,供CPU执行
而与CPU存取操作无关。
建议看下微机原理-》CPU如何读写寄存器
------解决方案--------------------
计算机内存分配与管理,计算机组成原理,了解CPU的总线周期,这问题你就明白了
------解决方案--------------------
探讨

回复5楼:
如果#pragma pack(n)并不会改变cpu访问内存的对齐方式,那么为什么还要进行这样一个让cpu降低效率的工作呢?难道只是为了节约一点内存空间吗?