字节对齐

原文:https://blog.csdn.net/cclethe/article/details/79659590(稍有修改)

基本概念
许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间的接口的硬件设计。对齐跟数据在内存中的位置有关。

如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。

为什么要字节对齐
需要字节对齐的根本原因在于CPU访问数据的效率问题。例如,假设一个处理器在读取数据的时候,比如他要读取一个long类型的数据,因为long长度为8字节,所以它读取数据的方式是从0地址开始每次以8字节为单位向这个long数据的地址前进,直到前进到这个数据的地址,并从这个地址开始读取出8字节,所以如果这个地址是8的倍数的话,就可以一次性把数据读出来,否则,我们可能需要执行两次存储器访问,因为对象可能被分放在两个8字节存储块中。对struct/class/union而言,每个成员的地址都要等于其最大成员类型大小的整数倍!所以一般要求把结构体中最大成员放在最前面,这样第一个最大成员对齐后,紧接着它的第二个成员的地址就不用偏移(offset),再后面的成员地址再发生偏移以使其对齐到最大成员大小的整数倍。

另外,假设一个int变量的地址不是自然对齐(int长度4字节,这个地址不是4的倍数),比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,以short的长度(2字节)为步长,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据;如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。

如何处理字节对齐
先让我们看编译器是按照什么样的原则进行对齐的:

数据类型自身的对齐值:为指定平台上基本类型的长度。对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
指定对齐值:#pragma pack (value)时的指定对齐值value。
数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合 :按其包含的长度最大的数据类型对齐。
结构体: 结构体中每个数据类型都要对齐。

当数据类型为结构体时,编译器可能需要在结构体字段的分配中插入间隙,以保证每个结构元素都满足它的对齐要求。具体有2个要求:

  1. 第一个数据变量的起始地址就是数据结构的起始地址,要保证这个起始地址是对齐值的整数倍。(在最近的对齐值整数倍地址前填充一些字节)
  2. 结构体的成员变量的地址也要对齐到对齐值的整数倍,所以对某些较小的成员需要在其前面填充一些字节,保证其在对齐位置上

通过上面的分析,对结构体进行字节对齐,我们需要知道四个值:

指定对齐值:代码中指定的对齐值,记为packLen
默认对齐值:结构体中每个数据成员及结构体本身都有默认对齐值,记为defaultLen
成员偏移量:即相对于结构体起始位置的长度,记为offset
成员长度:结构体中每个数据成员的长度(注结构体成员为补齐之后的长度),记为memberLen
及两个规则:

对齐规则: offset % vaildLen = 0,其中vaildLen为有效对齐值vaildLen = min(packLen, defaultLen);
填充规则: 如成员变量不遵守对齐规则,则需要对其补齐;在其前面填充一些字节保证该成员对齐。需填充的字节数记为pad

 

posted @ 2023-04-02 23:32  大黑耗  阅读(90)  评论(0编辑  收藏  举报