字节对齐

内容提要windows

l        字节对齐概念网络

l        字节对齐测试数据结构

n        offsetof测试

n        缺省状况的字节对齐优化

n        double 型字节对齐ui

n        改变字节对齐设置编码

l        不一样环境下的字节对齐spa

n        GCC字节对齐操作系统

n        ADS 字节对齐指针

l        字节对齐练习 

 

字节对齐是一个很隐含的概念,平时可能你没有留意,可是若是你在编写网络通信程序或者用结构去操做文件或硬件通信结构,这个问题就会浮出水面。我记得第一次致使我去看字节对齐概念资料的缘由就是ARP通信,ARP包头是一个31Byte包头。当你用一个认为是31Byte结构去处理数据包时,却老是处理不对。这一篇文章详细讨论了字节对齐要领和各类状况.

字节对齐概念

 

l 现代计算机中内存空间都是按照byte划分的,从理论上讲彷佛对任何类型的变量的访问能够从任何地址开始,但为了CPU访问数据的快速,一般都要求数据存放的地址是有必定规律的.

l 好比在32CPU,通常要求变量地址都是基于4,这样能够保证CPU用一次的读写周期就能够读取变量.不按4位对齐,若是变量恰好跨4位编码,这样须要CPU两个读写周期.效率天然低下.所以,在现代的编译器都会自动把复合数据定义按4位对齐,以保证CPU以最快速度读取,这就是字节对齐(byte Alignment)产生的背景

l 字节对齐是一种典型,以空间换时间的策略的,在现代计算机拥有较大的内存的状况,这个策略是至关成功的.

 

为何要字节对齐?

l        加快程序访问速度

l        不少CPU对访问地址有严格要求,这时编译器必需要这个CPU的规范来实现,X86较为宽松,不对齐结构可能只影响效率,ARM,访问地址必须基于偶地址,MIPSSparc也相似,这样不对齐的地址访问会形成错误.

 

关于字节对齐的实现

在不一样的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来分析结构和字节对齐

 

缺省状况的字节对齐

缺省的状况咱们是指32Bit CPU,Windows 使用VC++ 6.0,用这个环境基本能说明问题,其他的环境有不一样的,再补充说明.

 

对齐有以下状况:

1.      基本类型变量起始地址要按必定规则对齐.

l        char 类型,其起始地址要1字节边界上,即其地址能被1整除(即任意地址便可)

l        short类型,其起始地址要2字节边界上,即其地址能被2整除

l        int  类型,其起始地址要4字节边界上,即其地址能被4整除

l        long类型,其起始地址要4字节边界上,即其地址能被4整除

l        float类型,其起始地址要4字节边界上,即其地址能被4整除

l        double类型,其起始地址要8字节边界上,即其地址能被8整除

 

2.      结构实例起始址要在本身最大尺寸成员的对齐地址上

   如最大尺寸的成员是short,则要基于2对齐

3.      结构内成员的偏移量也要参照第1,知足相应倍数

如成员是short,则偏移量也是2的倍数.

这一条实际仍然是第1条规则的扩展,由于结构起始地址按最大倍数来,加上内部相应倍数,这样成员绝对地址仍然知足第1条规定

4.      结构总尺寸也要对齐要为最大尺寸的成员的整数倍,

若是不是则要在结构最后补齐成整数倍

 

关于第一条,咱们作以下测试

{

        char c1;

        int  i1;

       short o1;

       double d1;

#define ADDR_DIFF(a,b) ((char*)a) - ((char *)b)   

 

printf("c1 addr=0x%x,i1 addr=0x%x,o1 addr=0x%x,d1 addr=0x%x/n",

&c1,&i1,&o1,&d1);

printf("c1-i1 =%d,i1-o1=%d,o1-d1=%d/n",

ADDR_DIFF(&c1,&i1),ADDR_DIFF(&i1,&o1),ADDR_DIFF(&o1,&d1));

}

 

Win32下测试结果:

c1 addr=0x12ff7c, i1 addr=0x12ff78,o1 addr=0x12ff74,d1 addr=0x12ff6c

c1-i1 =4,i1-o1=4,o1-d1=8

从测试结果能够看出,编译器并无紧密的把各个数据结构排列在一块儿,而是按其对齐地址进行分配

 

结构的字节对齐

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取消对齐

l        #pragma pack (n) 设置对齐宽度为n,它能够是1,2,4,8等等,其中1就表示不进行字节对齐.

n        # pragma pack (n)是成片生效的,即在这个指令后面全部结构都会按新的对齐值进行对齐

l        # pragma pack() 将上一次# pragma pack (n)的设置取消.恢复为默认值.

l        二者是成对使用,在这二者之间全部结构均受到影响

 

   注意是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还有其它功能

l        #pragma pack(push) // 将当前pack设置压栈保存

l        #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 字节控制

l        #pragma pack (n)gcc将按照n个字节对齐

l        #pragma pack (),取消自定义字节对齐方式

 

#pragma 只保证的成员相关偏移量是字节对齐的.不保证绝对地址对齐.

 

GCC也支持某个一个数据结构实现绝对地址的天然对齐

__attribute((aligned (n))) 让所做用的结构成员对齐在n字节天然边界上。若是结构中有成员的长度大于n,则按照最大成员的长度来对齐。

__attribute__ ((packed)),取消结构在编译过程当中的优化对齐,按照实际占用字节数进行对齐。

 

struct STRUCT_TEST
 {
 char a;
  int b;
  char c;
 }  __attribute__ ((packed)); //注意位置,};之间

l        __attributeGCC属性,跟#pragma 不一样, __attribute__gcc的方言,只有GCC能识别,不要在VC++之类编译器使用这种定义.

l        __attribute每次只对一个结构生效.

 

 

 

ADS的字节对齐控制

 

ARM对访问地址有特殊要求,若是不对齐,会形成程序错误,而不是象X86PowerPC那样折成两个指令访问所以用#pragma pack(1) 只是让结构自己成员内部按1对齐,并不能保证结构的绝对地址是对齐.

 ADS采用特殊指令来实现要想保证地址对齐.ADS采用ALIGN.__align(num), .__packed,来控制字节对齐

l        ALIGN 用于汇编的字节对齐控制

l        __align(num) 相似于#pragma pack(num),用于整片代码字节对齐的的控制.

l        __packed 取消某个结构或成员的内部字节对齐,并实现绝对地址对齐,相似于gcc__attribute__ ((packed));

 

     __packed struct STRUCT_TEST
 {
 char a;
  int b;
  char c;
 }  ;

 

字节对齐练习

请指在windows 32下出下列值的sizeof和各个成员的偏移量

1. struct  s1{

   short a;

   short b;

   short c;

};

 

2. struct s2{

    char a[21];

    short b;

};

3. struct s2{

    float a;

    char b;

    short c;

    int d;

};

 

5.      #pragma pack (1)

typedef struct s8

{

 char a;

 int b;

 short c;

 double d;

}s8;

#pragma pack ()