菜鸟笔记
提升您的技术认知

结构体对齐(字节对齐)规则及大小计算

什么是字节对齐

这跟读取数据有关,cpu读取一次能读取到的内存大小跟数据总线的位数有关,如果数据总线为16位,那么cpu一次能够读取2字节;如果为32位那么cpu一次可以读取4字节,而读取数据是需要消耗时间的,为了提高效率,尽量让同一个数据(变量)能使用最少次数将其读取出来,那么解决办法就是要求每个数据(变量)在其自然边界上,比如说一个int类型的变量占4字节,那么在存储这个int变量的时候编译器会将让这个变量的起始地址能够被4整除,那么这样就不会导致这个int类型的变量明明没有超过数据总线位数(假设位32位)而需要cpu两次才能将其读取出来,这就是字节对齐。

为什么要进行字节对齐

上面也说了,为了提高效率。进行字节对齐还有另一个原因,那就是平台的原因,其实不是所有的硬件平台都能够访问任意地址上的任意数据,所以这就导致了进行字节对齐的必要性。尤其是结构体,结构体会包含不同类型的数据,如果不进行认为的(编译器做的事)字节对齐,那么就会导致数据不在他的自然边界上,会影响效率,甚至会引发硬件错误。

字节对齐规则

这里讲结构体对齐规则,结构体对齐包括字节对齐和结构的整体对齐。

1、字节对齐

字节对齐是针对结构体内的数据的对齐,程序员可以使用预处理指令# parama pack(n)来设定默认对齐数值,其中n值就是设置的大小(值位1,2,4,8...),数据成员本身也有一个字节大小,编译器会选择这两个中小的那个数值作为对齐大小。第一个数据成员从offset(偏移量)为0的地方开始存储。

比如:

#parama pack(2)

int a;

a 为整型,占4字节,比预处理指令指定的2大,所以按照2来对齐,也就是说,存储a的起始地址必须要能被2整除。

2、整体对齐

整齐对齐也很简单,整体对齐也会比较两个数值的大小,第一个同样还是预处理指令指定的大小n,第二个是所有结构体的数据成员中字节最大的那个,编译器会选择两个中小的那个,我们设它为值m。选取之后,编译器会去查看结构体的最后一个数据成员存储之后的后一个地址是否为选取m的倍数,如果是m的倍数则什么都不需要做,如果不是,那么需要补齐,使得补齐之后的地址能被m整除(为m倍数)。

对齐举例

见如下的结构体。

#include<iostream>
using namespace std;
 
#pragma pack(2)
struct AA{
    int a;
    char b;
    short c;
    char d;
};
#parama pack();

第一步:a占4字节,比预设的2大所以将2作为对齐大小,起始地址0是2倍数,所以a存储的位置区间为[0,3]。

第二步:b占1字节,比2小,所以将1作为对齐大小,如果紧挨着a存储b,那么起始地址为4,4是1的倍数,所以存储的b位置区

              间为[4]。

第三步:c占2字节,所以对齐大小为2,如果紧挨着c存储,那么起始地址为5,而5不是2的倍数,6是2的倍数,所以需要补一

              位,从6开始存储c,所以存储c的位置区间为[6,7]。

第四步:字节d占1字节,将1作为对齐大小,8是1的倍数,所以可以紧挨着c存储d,所以存储d的位置区间为[8]。

第五步:所有数据成员存储完后,需要进行整体对齐,数据成员中占最大字节的是int a,占4字节,比预设的2大,所以将2作为

              对齐大小,而存储完最后一个数据成员d之后的地址为9,9不是2的倍数,需要补齐。

所以最后结构体占的区间为[0,9],也就是说结构体的size为10。

总结

因为是按照顺序存储结构体中的成员,所以即使结构体中的数据成员完全一样,而他们的相对位置顺序不一样的话,那么他们所占的大小也很可能不同。

比如下面的结构体和上面的结构体中的数据成员一模一样,只是顺序位置不一样,下面结构体的size却是8。

#include<iostream>
using namespace std;
 
#pragma pack(2)
struct AA{
    int a;
    char b;
    char d;
    short c;
};
#parama pack();