简单聊一聊结构体大小与字节对齐
什么是字节对齐
在结构体中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的“对齐”,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做「自然对齐」。
现在计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要「各种类型数据按照一定的规则在空间上排列」,而不是顺序的一个接一个排放,这就是对齐。
为什么需要字节对齐?
「各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取」
比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生拒绝访问或抛出硬件异常,那么在这种架构下编程必须保证字节对齐,但其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。 比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
「使用字节对齐将会提高CPU访问内存的效率」 假设一个整型变变量的地址不是自然对齐,那么CPU读取他它的值就需要访问两次内存,而如果变量在自然对齐的位置上,则只需要一次就可以去除取出数据。某些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误,而在x86上就不会出现错误,只是效率下降。
同时合理的利用字节对齐可以「有效地节省存储空间」。
面对字节对齐问题,编程中如何设计?
如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间。 还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做法是显式的插入reserved成员:
struct A{
char a;
char reserved[3]; //使用空间换时间
int b;
}
reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用。
字节对齐可能带来的隐患
代码中关于对齐的隐患,很多是隐式的,比较常见的如强制类型转换后访问数据的时候,在如Alpa、IA-64、MIPS等CPU架构中将拒绝访问或抛出硬件异常。
字节对齐三个准则
结构体变量的首地址能够被其有效对齐值的大小所整除 结构体的总大小为结构体有效对齐值的整数倍 结构体每个成员相对于结构体首地址的偏移量都是有效对齐的整数倍
C++中可以通过#pragma pack(n)
来设定变量以n字节对齐。
注:
自身对齐值:数据类型本身的对齐值,结构体(类)的自身对齐值是其成员中最大的那个值,例如,char类型的自身对齐值是1,short类型的自身对齐值是2 指定对齐值:编译器或程序指定的对齐值,32位单片机的指定对齐值默认值为4 有效对齐值:min(自身对齐值,指定对齐值)