字节对齐Andrew Huang<bluedrum@>内容提要●字节对齐概念●字节对齐测试⏹offsetof⏹缺省情况的字节对齐⏹double 型字节对齐⏹改变字节对齐设置●不同环境下的字节对齐⏹GCC字节对齐⏹ADS 字节对齐●字节对齐练习字节对齐是一个很隐含的概念,平时可能你没有留意,但是如果你在编写网络通讯程序或者用结构去操作文件或硬件通讯结构,这个问题就会浮出水面。
我记得第一次导致我去看字节对齐概念资料的原因就是ARP通讯,ARP包头是一个31Byte包头。
当你用一个认为是31Byte结构去处理数据包时,却总是处理不对。
这一篇文章详细讨论了字节对齐要领和各种情况.字节对齐概念●现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但为了CPU访问数据的快速,通常都要求数据存放的地址是有一定规律的.●比如在32位CPU上,一般要求变量地址都是基于4位,这样可以保证CPU用一次的读写周期就可以读取变量.不按4位对齐,如果变量刚好跨4位编码,这样需要CPU两个读写周期.效率自然低下.因此,在现代的编译器都会自动把复合数据定义按4位对齐,以保证CPU以最快速度读取,这就是字节对齐(byte Alignment)产生的背景●字节对齐是一种典型,以空间换时间的策略的,在现代计算机拥有较大的内存的情况,这个策略是相当成功的.为什么要字节对齐?●加快程序访问速度●很多CPU对访问地址有严格要求,这时编译器必须要这个CPU的规范来实现,X86较为宽松,不对齐结构可能只影响效率,如ARM,访问地址必须基于偶地址,MIPS和Sparc也类似,这样不对齐的地址访问会造成错误.关于字节对齐的实现在不同的CPU对地址对齐有不同要求,各个编译器也会采用不同策略来实现字节对齐,在随后的例子,可以对比PC下的Windows,和Linux,以有ARM下的字节对齐策略.字节对齐带来的问题字节对齐相当于编译器自已在开发者定义的结构里偷偷加入一些填充字符,而且各种编译器填充的策略不一定相同.因此,在网络传输,二进制文件处理以及.底层总线传输和底层数据等相关领域,忽略字节对齐会带来严重问题.这样会产生错位使用程序处理数据完全错误.因此,网络以及硬件相关开发人员必须对字节对齐要有清晰的了解.字节对齐测试offsetof 操作符在分析字节对齐之前,首先了解一下offsetof宏.这个宏是标准C的定义,每个C库均会在stddef.h中定义.作用是计算结构或联合每一个成员的偏移量.用offsetof我们可以很清晰看到字节是如何对齐的.它的用法如下:typedef struct { char c1; int i1; char c2; } S3;printf(“c1 offset=%d,i1 offset =%d,c2 offset=%d/n”,offsetof(S3,c1),offsetof(S3,i1),offsetof(S3,c2));offsetof在不同操作系统下定成不同形式./* Keil 8051 */#define offsetof(s,m) (size_t)&(((s *)0)->m)/* Microsoft x86 */#ifdef _WIN64#define offsetof(s,m) (size_t)( (ptrdiff_t)&( ( (s *)0 )->m ) )#else#define offsetof(s,m) (size_t)&( ( (s *) 0 )->m )#endif/* Motorola coldfire */#define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))/* GNU GCC 4.0.2 */#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)注意:offsetof 不能求位域成员的偏移量,offsetof 虽然引用了一个空指针来操作成员,但是由于只是在取类型,并且这个值在编译期就被确定,所以编译器在编译会直接算出offsetof的值,而不会在运行期引起内存段错误.以下我们用offsetof来分析结构和字节对齐缺省情况的字节对齐从测试结果可以看出,编译器并没有紧密的把各个数据结构排列在一起,而是按其对齐地址进行分配结构的字节对齐例1:typedef struct s2{int a;short b;char c;}s2;printf("s2 size=%d,int a=%d,short b=%d,char c=%d/n",sizeof(s2),offsetof(s2,a),offsetof(s2,b),offsetof(s2,c));测试结果是s2 size=8,int a=0,short b=4,char c=6从结果看.是总尺寸是8,各成员尺寸之和是7,从偏移量可以看在最后补齐一个字符,这是按规则4,总尺寸是最大成员倍数例2:typedef struct s5{int a;char b;short c;}s5;printf("s5 size=%d,int a=%d,char b=%d,short c=%d/n",sizeof(s5),offsetof(s5,a),offsetof(s5,b),offsetof(s5,c));测试结果是s5 size=8,int a=0,char b=4,short c=6这一次补齐的目的是为了short 型的c基于2对齐,应用第3条规则例3:typedef struct s10{char b;int a;short c;}s10;printf("s10 size=%d,char b=%d,int a=%d,short c=%d/n",sizeof(s10),offsetof(s10,b),offsetof(s10,a),offsetof(s10,c));测试结果: s10 size=12,char b=0,int a=4,short c=8第一次补齐的目的是为了int 型的a基于4对齐,应用第3条规则第二次补齐为了合符第4条规则.要为int的倍数.例5:typedef struct s4{char a;short b;char c;}s4;printf("s4 size=%d,int a=%d,short b=%d,char c=%d/n",sizeof(s4),offsetof(s4,a),offsetof(s4,b),offsetof(s4,c));测试结果: s4 size=6,int a=0,short b=2,char c=4这里最大尺寸的成员是short b所以总尺寸是2的倍数,而且short本身也需要2对齐,因此在两个不同地方补了一个byte double型的字节对齐先看测试样例typedef struct s1{char a;double b;short c;}s1;printf("s1 size=%d,char a=%d,double b=%d,short c=%d/n",sizeof(s1),offsetof(s1,a),offsetof(s1,b),offsetof(s1,c));在Windows +VC 6.0下测试结果: s1 size=24,char a=0,double b=8,short c=16在Redhat 9.0 +gcc 3.2.2下测试结果: s1 size=16,char a=0,double b=4,short c=12可以看到在两个编译器上,对double的对齐处理不一样.在Linux下,double 采用是基于4对齐.而Windows采用8对齐.再看一个实例typedef struct s1{char a;double b;char c;int d;}s1;printf("s6 size=%d,char a=%d,double b=%d,char c=%d int d=%d/n",sizeof(s6),offsetof(s6,a),offsetof(s6,b),offsetof(s6,c),offsetof(s6,d));在Windows +VC 6.0下测试结果: s6 size=24,char a=0,double b=8,char c=16 int d=20在Redhat 9.0 +gcc 3.2.2下测试结果: s6 size=20,char a=0,double b=4,char c=12 int d=16改变字节对齐设置默认的字节对齐都是按最大成员尺寸来进行对齐,但是在开发中可能需要调整对齐宽度.最常的一种情况是,在在网络和底层传输中取消字节对齐,完成按原始尺寸紧密的排列.还有一种情况是扩大或缩少字节对齐的排列.这种情况比较复杂.但应用比较少.取消字节对齐在文件处理,网络和底层传输中,数据都是紧密排列.不希望编译器在结构内部自行增加空间.这时需要开发者通知编译器,某一些结构是不需要字节对齐的.绝大部分编译器是使用预编译指令pragma取消对齐●#pragma pack (n) 设置对齐宽度为n,它可以是1,2,4,8等等,其中1就表示不进行字节对齐.⏹# pragma pack (n)是成片生效的,即在这个指令后面所有结构都会按新的对齐值进行对齐●# pragma pack()将上一次# pragma pack (n)的设置取消.恢复为默认值.●两者是成对使用,在这两者之间所有结构均受到影响注意是pragma,不是progma例子:#pragma pack(1)typedef struct s7{int a;short b;char c;}s7;#pragma pack()printf("s7 size=%d,int a=%d,short b=%d,char c=%d/n",sizeof(s7),offsetof(s7,a),offsetof(s7,b),offsetof(s7,c));测试结果s7 size=7,int a=0,short b=4,char c=6可以看到,取消字节对齐,sizeof()就成员尺寸之和.改变字节对齐这种情况比较复杂,而且也不常用.也是通过#pragma pack(n)来完成生效,但是要注意,字节对齐值采用n和默认对齐值中较小的一个.换句话说,扩大对齐值是不生效的.#pragma pack还有其它功能●#pragma pack(push) // 将当前pack设置压栈保存●#pragma pack(pop) // 恢复先前的pack设置这两个功能用于多种对齐值混用的场合,(当然,这种情况也是非常少见)缩小例子:#pragma pack (2) /*指定按2字节对齐,缺省是4 */typedef struct s8{char a;int b;short c;}s8;#pragma pack ()printf("s8 size=%d,char a=%d,int b=%d,short c=%d/n",sizeof(s8),offsetof(s8,a),offsetof(s8,b),offsetof(s8,c));测试结果: s8 size=8,char a=0,int b=2,short c=6缺省的4字节对齐话,sizoef应该是12,现在改为2对齐的话,只能是8,即在char a 补了一个字节.扩大的例子:#pragma pack (8) /*指定按2字节对齐,缺省是4 */typedef struct s9{char a;int b;short c;}s9;#pragma pack ()printf("s9 size=%d,char a=%d,int b=%d,short c=%d/n",sizeof(s9),offsetof(s9,a),offsetof(s9,b),offsetof(s9,c));测试结果:s9 size=12,char a=0,int b=4,short c=8这个结果跟4对齐是一样的,换句话说,8对齐没有生效不同环境下的字节对齐使用GCC的字节对齐控制GCC也支持#pragma 字节控制●#pragma pack (n),gcc将按照n个字节对齐●#pragma pack (),取消自定义字节对齐方式#pragma 只保证的成员相关偏移量是字节对齐的.不保证绝对地址对齐.GCC也支持某个一个数据结构实现绝对地址的自然对齐__attribute((aligned (n))) 让所作用的结构成员对齐在n字节自然边界上。