结构体边界对其是一个老生常谈的话题了,网上的解释非常多,但大多偏重于讲步骤,对于每一步的原因都有点不清楚的地方,下面结合网上的讲解和自己的理解谈谈结构体对齐,不一定正确。
1.什么是结构体对齐。
struct A{
char a;
char b;
int c;
char d;
}
对于上面的这个结构体,假设机器字长32位(4字节),那么该结构体变量占用的空间并非1+1+4+1=7,而是1+3+4+1+3=12。编译器自动将c的起始位置调整到第4个字节处,并在结构体最后加了3个字节,这种编译器调整变量内存地址的现象就是结构体对齐。
2.为什么要结构体对齐。
cpu的寻址并不是可以随机访问的,我们把一个字节看作一个内存单元,给每个内存单元添加连续的标号,构成了内存地址,cpu对内存的访问限制取决于其机器字长,一般来说,若机器字长为N,那cpu访问内存时每次都是读取以N的整数倍为地址的连续N个内存单元(这个好像在计算机组成原理cpu寻址那块也有体现),所以,以结构体A为例,若其变量的成员变量c的存储位置不进行调整,仍然从第3个字节开始存储,那么cpu要访问a就要寻址两次,第一次访问地址为0的连续4个内存单元,后2个单元的值是c的一部分,然后还要访问地址为4的连续4个内存单元,前2个单元为c的一部分。若进行内存对齐,将变量c放在机器字长整数倍的位置,则只需一次访问即可。
3.一点思考。
上面的讲的对齐方法有一点没有提到,既然将变量c放在机器字长的整数倍的位置上,为什么变量b没有这样做,将所有变量都按机器字长的整数倍存储不就行了吗。我的理解是,在访问内存单元时,最大的开销在于总线的占用和传输,至于在cpu内部对数据的处理相比之下开销几乎不存在,故对于长度小于机器字长的变量来说,其存储位置只要满足是自己长度的整数倍即可,这样就能保证cpu能一次内存访问就获得全部数据(感觉是这样的,因为所有基础变量包括机器字长相互之间是整除与被整除的关系,长度规律是1,2,4,8,16,32……,虽然不会严格证明,但想不出满足该规律结果还分两次访问的反例),然后再cpu内部经过极小的代价将其提取出来即可,这样时间成本相比按机器字长整数倍存储只增加了一点点(因为都是只靠一次内存访问就能获取数据,而cpu内部处理的花费很小),但是节省了存储空间,试想若变量很多,那么节省的空间是很可观的。
4.总结结构体成员对齐的步骤
确定机器字长,结构体内每个变量与机器字长比较,取较短的那个值作为各自的对其系数进行对齐,这里要注意一点,最后要取结构体内最长变量的长度与机器字长中较小的那个数对结构体大小做圆整,这样是为了保证使用结构体数组时使每个数组元素都能对齐。