一.什么是字节对齐,为什么要对齐?
现代计算机中内存空间都是依照byte区分的,从理论上讲好像对任何类型的变量的拜访能够从任何地址开端,但实际状况是在拜访特定类型变量的时分经常在特定的内存地址拜访,这就需求各种类型数据依照必定的规则在空间上摆放,而不是次序的一个接一个的排放,这便是对齐。 对齐的效果和原因:各个硬件渠道对存储空间的处理上有很大的不同。一些渠道对某些特定类型的数据只能从某些特定地址开端存取。比方有些架构的CPU在拜访一个没有进行对齐的变量的时分会产生过错,那么在这种架构下编程有必要确保字节对齐.其他渠道或许没有这种状况,可是最常见的是假如不依照合适其渠道要求对数据寄存进行对齐,会在存取功率上带来丢失。比方有些渠道每次读都是从偶地址开端,假如一个int型(假定为32位系统)假如寄存在偶地址开端的当地,那么一个读周期就能够读出这32bit,而假如寄存在奇地址开端的当地,就需求2个读周期,并对两次读出的成果的凹凸字节进行凑集才干得到该32bit数据。明显在读取功率上下降许多。
二.字节对齐对程序的影响:
先让咱们看几个比方吧(32bit,x86环境,gcc编译器):
设结构体如下界说:
struct A{int a;char b;short c; };
struct B { char b;int a;short c; };
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同) short:2(有符号无符号同) int:4(有符号无符号同) long:4(有符号无符号同) float:4 double:8
那么上面两个结构巨细怎么呢?
成果是: sizeof(strcut A)值为8 sizeof(struct B)的值却是12 结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也相同;按理说A,B巨细应该都是7字节。 之所以呈现上面的成果是由于编译器要对数据成员在空间上进行对齐。
上面是依照编译器的默许设置进行对齐的成果,那么咱们是不是能够改动编译器的这种默许对齐设置呢,当然能够.
例如: #pragma pack (2) /*指定按2字节对齐*/ struct C { char b; int a; short c; }; #pragma pack () /*撤销指定对齐,康复缺省对齐*/ sizeof(struct C)值是8。
修正对齐值为1: #pragma pack (1) /*指定按1字节对齐*/ struct D { char b; int a; short c; }; #pragma pack () /*撤销指定对齐,康复缺省对齐*/ sizeof(struct D)值为7。
后边咱们再解说#pragma pack()的效果.
三.编译器是依照什么样的准则进行对齐的?
先让咱们看四个重要的根本概念:
1.数据类型自身的对齐值: 关于char型数据,其自身对齐值为1,关于short型为2,关于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或许类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有用对齐值:自身对齐值和指定对齐值中小的那个值。 有 了这些值,咱们就能够很便利的来评论详细数据结构的成员和其自身的对齐方法。有用对齐值N是终究用来决议数据寄存地址方法的值,最重要。有用对齐N,便是表明“对齐在N上”,也便是说该数据的”寄存开端地址%N=0″.而数据结构中的数据变量都是按界说的先后次序来排放的。第一个数据变量的开端地址便是数据结构的开端地址。结构体的成员变量要对齐排放,结构体自身也要依据自身的有用对齐值圆整(便是结构体成员变量占用总长度需求是对结构体有用对齐值的整数倍,结合下面比方了解)。这样就不难了解上面的几个比方的值了。
比方剖析:
剖析比方B;
struct B { char b; int a; short c; };
假 设B从地址空间0x0开端排放。该比方中没有界说指定对齐值,在笔者环境下,该值默许为4。第一个成员变量b的自身对齐值是1,比指定或许默许指定对齐值4小,所以其有用对齐值为1,所以其寄存地址0x0契合0x0%1=0.第二个成员变量a,其自身对齐值为4,所以有用对齐值也为4,所以只能寄存在开端地址为0x4到0x7这四个接连的字节空间中,复核0x4%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为2,所以有用对齐值也是2,能够寄存在0x8到0x9这两个字节空间中,契合0x8%2=0。所以从0x0到0x9寄存的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以便是4,所以结构体的有用对齐值也是4。依据结构体圆整的要求,0x9到0x0=10字节,(10+2)%4=0。所以0x0A到0xB也为结构体B所占用。故B从0x0到0xB共有12个字节,sizeof(struct B)=12;其实假如就这一个就来说它已将满意字节对齐了, 由于它的开端地址是0,因而肯定是对齐的,之所以在后边弥补2个字节,是由于编译器为了完成结构数组的存取功率,试想假如咱们界说了一个结构B的数组,那么第一个结构开端地址是0没有问题,可是第二个结构呢?依照数组的界说,数组中所有元素都是紧挨着的,假如咱们不把结构的巨细弥补为4的整数倍,那么下一个结构的开端地址将是0x0A,这明显不能满意结构的地址对齐了,因而咱们要把结构弥补成有用对齐巨细的整数倍.其实比方:关于char型数据,其自身对齐值为1,关于short型为2,关于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是根据数组考虑的,仅仅由于这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,剖析上面比方C: #pragma pack (2) /*指定按2字节对齐*/ struct C { char b; int a; short c; }; #pragma pack () /*撤销指定对齐,康复缺省对齐*/ 第 一个变量b的自身对齐值为1,指定对齐值为2,所以,其有用对齐值为1,假定C从0x0开端,那么b寄存在0x0,契合0x0%1=0;第二个变量,自身对齐值为4,指定对齐值为2,所以有用对齐值为2,所以次序寄存在0x2、0x3、0x4、0x5四个接连字节中,契合0x2%2=0。第三个变量c的自身对齐值为2,所以有用对齐值为2,次序寄存 在0x6、0x7中,契合0x6%2=0。所以从0x0到0x07共八字节寄存的是C的变量。又C的自身对齐值为4,所以C的有用对齐值为2。又8%2=0,C只占用0x0到0x7的八个字节。所以sizeof(struct C)=8.
四.怎么修正编译器的默许对齐值?
1.在VC IDE中,能够这样修正:[Project][Settings],c/c++选项卡Category的Code Generation选项的StructMember Alignment中修正,默许是8字节。 2.在编码时,能够这样动态修正:#pragma pack .留意:是pragma而不是progma.
五.针对字节对齐,咱们在编程中怎么考虑?
假如在编程的时分要考虑节省空间的话,那么咱们只需求假定结构的首地址是0,然后各个变量依照上面的准则进行摆放即可,根本的准则便是把结构中的变量依照类型巨细从小到大声明,尽量削减中心的添补空间.还有一种便是为了以空间交换时刻的功率,咱们显现的进行添补空间进行对齐,比方:有一种运用空间换时刻做法是显式的刺进reserved成员: struct A{ char a; char reserved[3];//运用空间换时刻 int b; }
reserved成员对咱们的程序没有什么含义,它仅仅起到添补空间以到达字节对齐的意图,当然即便不加这个成员一般编译器也会给咱们主动添补对齐,咱们自己加上它仅仅起到显式的提示效果.
六.字节对齐或许带来的危险:
代码中关于对齐的危险,许多是隐式的。比方在强制类型转化的时分。例如: unsigned int i = 0x12345678; unsigned char *p=NULL; unsigned short *p1=NULL; p=&i; *p=0x00; p1=(unsigned short *)(p+1); *p1=0x0; 最终两句代码,从奇数鸿沟去拜访unsignedshort型变量,明显不契合对齐的规则。 在x86上,相似的操作只会影响功率,可是在MIPS或许sparc上,或许便是一个error,由于它们要求有必要字节对齐.
七.怎么查找与字节对齐方面的问题:
假如呈现对齐或许赋值问题首要检查 1. 编译器的big little端设置 2. 看这种系统自身是否支撑非对齐拜访 3. 假如支撑看设置了对齐与否,假如没有则看拜访时需求加某些特别的润饰来标志其特别拜访操作。
八.