结构体对齐

来源:互联网 发布:windows常用网络命令 编辑:程序博客网 时间:2024/05/22 03:44

        实际在考虑到程序编写过程中的对齐,还需要引出一个重要的概念:对齐参数。对于不同的系统,默认的对齐参数是不一样的,Win32平台下的微软VC编译器在默认情况下采用如下基本数据类型T的大小,即sizeof(T)。Linux下的GCC奉行的是另外一套规则:任何2字节大小(包括单字节)的数据类型(比如short)的对齐参数就是sizeof(T),而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。了解了对齐参数之后,我们对齐的规则是:每个成员按其类型的大小和指定对齐参数(这里默认是8字节)中较小的一个对齐。并且结构的总长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

     

结构体内存分配例子一:

struct test1{       char a;        int b;       char c;    }

测试可知,


sizeof(struct test1)=12;


 offsetof(struct test1,a)=0;


offsetof(struct test1,b)=4;


offsetof(struct test1,c)=8;  注:offsetof是用来判断一个成员在内存中分配的位置距离整个结构体开始位置的偏移量


结构体第一个成员为char类型,它的对齐参数为:1,它可以放到任何位置(任何数都可以是1的整数倍),占用一个字节。


b是整型,它的对齐参数是4,所以它需要放到4的整数倍的位置。因为起始的地方肯定是可以被4整除的,所以最近的一个4的整数是a的地址+4,所以需要在a后填充3个空白字节,然后放b。


c同a,占用一个字节。所以这个结构体目前所占的长度为:1+3+4+1=9。根据前面的说法,结构体的总长度必须为使用过的所以的对齐参数的整数倍,其实就是max(所有的对齐参数),在本例子中就是4,那么这个结构体的总长度必须为4的整数倍,9最近的就是12,所以在c后面补充3个空白字符!!!这个结构体的实际利用率只有50%。


是不是很简单呢?在网上很多帖子中,有人说结构体的总长度必须是结构体中所有类型的最长大小的整数倍。其实在gcc这就是不对的,自己思考为什么。想想这个例子


struct t{      int a;double b;}在gcc环境下是12   ---------12%8!=0


结构体内存分配例子二:

1 strcut test22 {3      int a ;4      char b[9];5      char c;   6 }

测试可知:


[pengliang@www 2013-06-05]$ ./structlength
sizeof(struct test2)=16
offsetof(struct test2)=0
offsetof(struct test2)=4
offsetof(struct test2)=13
a占四个字节,b是一个数组,对于结构体中的数组,只需要查看它中的类型。因为b数组中存放的是char类型,char的对齐参数是1,所以这个数组中元素不管前面存放的是什么,b中第一个元素总是挨着前面那个元素。b占9个字节。同理:c是char 类型,紧挨在数组后面。4+9+1=14。前面使用到的所以对齐参数有:4和1,所以max(4,1)=4,结构体的总长度是4的倍数。c后面需要填充2个字节,为16

结构提内存分配例子三:

1 struct test32 {3         char a;4         short b[5];5         char c;6 }

 测试结果:


[pengliang@www 2013-06-05]$ ./structlength
sizeof(struct test3)=14
offsetof(struct test3,a)=0
offsetof(struct test3,b)=2
offsetof(struct test3,c)=12


char的对齐参数为1,short的对齐参数为2。a占1个字节,因为short对齐参数为2,所以a后面填充一个字节,然后开始b数组,b一共10个字节。c紧跟b。所以暂时的长度为:1+1+10+1=13。


使用的所以对齐参数最大的为2,所以总长度要为2的倍数,c后面再添加一个空白字符。总共14字节。

经过上面三个例子,我们应该对基本数据类型的结构体的内存分配有了一定的了解,那么如果在结构体中还存在结构体成员呢?

我们有如下的处理规律:


  数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。 (其实前面已经证实了这个情况)
  联合 :按其包含的成员中对齐参数最大的参数进行对齐。
  结构体: 结构体中每个数据类型都要对齐,且结构体的对齐参数是结构体成员中对齐参数最大的。

来一个例子:

1 struct s1 2 { 3         short a; 4         long b;       5 } 6  7 struct s2 8 { 9         char c;10         struct s1 struct1;11         short e;   12 }

[pengliang@www 2013-06-05]$ ./structtest
sizeof(struct s2) :16
offsetof(struct s2,c) : 0
offsetof(struct s2,struct1) :4
offsetof(struct s2,e) :12


在s2中,第一个成员为c,占用一个字节。然后一个成员为struct1,那么struct1的对齐参数为:max(2,4)=4{取这个结构体中所有的对齐参数的最大值},所以需要在c后面填充3个字节。然后开始放struct1,因为struct1是结构体,所以按照结构体的分配方式:a占两个字节,由于long的对齐参数为4,所以a后填充两个字节,然后开始摆放b。b占四个字节。e占两个字节。所以暂时的总长度为1+3+2+2+4+2=14。在结构体s2中使用的所以的对齐参数有:1 、4 、2,所以s2的长度为4的倍数。在e后面添加两个字节。14+2=16;


再来一个例子:

1 struct s12 {    char a;3   double b;4 }struct s2{  int c;    struct s1 struct1[3];    short d;}

 [pengliang@www 2013-06-05]$ ./structtest
sizeof(struct s2) :44
offsetof(struct s2,c) : 0
offsetof(struct s2,struct1) :4
offsetof(struct s2,d) :40
这个结果大家自己分析!!


联合(共同)体比较简单,我这里只举两个例子:

1   union test2    {3       char a;4       int c;5       double d;6    }; 
sizeof(union test):8(取最长的嘛,肯定是double啊)

1  struct s2   {3      double e;4     union test t1;5      char f;6       union test t2;7   }

[pengliang@www 2013-06-05]$ ./uniontest
sizeof(struct s):28
offsetof(struct s,e):0
offsetof(struct s,t1):8
offsetof(struct s,f):16
offsetof(struct s,t2):20

好了,这些内容就说到这里了,可能很多人为什么一样的代码在自己的机器上面结果不要呢?这个前面我就说了,对齐的时候每一个编译器的默认对齐参数不一样,在vc中全部都是sizeof(type),所以double的默认对齐参数是8.但是在gcc中,类型大于等于4的类型全部是4,这就是区别。另外,我们可以通过这个指令来调整这个默认值:


#pragma pack(n)     //n为对齐的默认字节

#pragma pack(1)1 struct test32 {3         char a;4         short b[5];5         char c;6 }
[pengliang@www 2013-06-05]$ ./uniontestsizeof(struct test3):12offsetof(struct test3,a):0offsetof(struct test3,b):1offsetof(struct test3,c):11由此可以看出,如果这个默认对齐参数越小,越有利于节约内存,但是可能会带来访问的效率问题。当n=1时,就是每一个变量都是一个挨着一个存放的!!!当默认对齐参数越大,占的空间就越多,主要是在8字的变量较多的情况下,但是这样的访问效率较好。这就是一个时间与空间的博弈!!

1  #pragma pack(8)2   3  struct s4  {   5    char a;6     7    short b[5];8    double c;9 }

这个sizeof(s)在vc下编译,结果是24,a占一个字节,补一个空,b占10个字节,补4个字节(因为b要从8整数倍开始),最后8个字节的c。1+1+10+4+8=24.


但是这个#pragma pack(1)在gcc下面好使,但是#pragma pack(8)却不好使!!!


查看论坛得知:默认的对齐是按照 int 型(4字节)对齐,如果指定 #pragma pack(N) 中的 N 的话,N 不能大于默认对齐指定的长度,即如果默认对齐是 4 的话,N的取值可以是 1、2、4,超过 4 之后作为 4 处理。在 Windows 等系统上似乎没有这个限制。-------http://bbs.chinaunix.net/thread-636323-1-1.html


那么怎么在gcc中设置一个结构体的存储对齐参数为8呢??


查找之后发现,在定义后面加上这个gcc特有的_attribute__机制即可:


1  #pragma pack(8)2   3  struct s4  {   5    char a;6     7    short b[5];8    double c;9 }__attribute__ ((aligned (8)));这样就能得到和

[pengliang@www 2013-06-05]$ ./uniontest
sizeof(struct test3):24
offsetof(struct test3,a):0
offsetof(struct test3,b):2
offsetof(struct test3,c):12
这样的话,在gcc中,这个存储结构也占用了24个字节。但是还是有点小问题:在vc中abc的偏移分别是:0、2、16.而在gcc中偏移量是0、2、12,呵呵,到这里,你应该能够分辨为什么会有这种区别了吧??




原创粉丝点击