深度解析求结构体sizeof问题

来源:互联网 发布:电脑办公软件自学 编辑:程序博客网 时间:2024/06/08 14:22

运算符sizeof可以计算出给定类型的大小,对于32位系统来说,sizeof(char) = 1; sizeof(int) = 4。基本数据类型的大小很好计算,我们来看一下如何计算构造数据类型的大小。       

       C语言中的构造数据类型有三种:数组、结构体和共用体。

       数组是相同类型的元素的集合,只要会计算单个元素的大小,整个数组所占空间等于基础元素大小乘上元素的个数。

       结构体中的成员可以是不同的数据类型,成员按照定义时的顺序依次存储在连续的内存空间。和数组不一样的是,结构体的大小不是所有成员大小简单的相加,需要考虑到系统在存储结构体变量时的地址对齐问题。看下面这样的一个结构体:

[cpp] view plain copy
 print?
  1. struct stu1  
  2. {  
  3.      int i;  
  4.      char c;  
  5.      int j;  
  6. };  

      用sizeof求该结构体的大小,发现值为12。int占4个字节,char占1个字节,结果应该是9个字节才对啊,为什么呢?

      先介绍一个相关的概念——偏移量。偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。因此,第一个成员i的偏移量为0。第二个成员c的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),其值为4;第三个成员j的偏移量是第二个成员的偏移量加上第二个成员的大小(4+1),其值为5。

      然而,在实际中,存储变量时地址要求对齐,编译器在编译程序时会遵循两条原则:

      (1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍) 

      (2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数

      上面的例子中前两个成员的偏移量都满足要求,但第三个成员的偏移量为5,并不是自身(int)大小的整数倍。编译器在处理时会在第二个成员后面补上3个空字节,使得第三个成员的偏移量变成8。结构体大小等于最后一个成员的偏移量加上其大小,上面的例子中计算出来的大小为12,满足要求。

       再来看另外一个例子:

[cpp] view plain copy
 print?
  1. struct stu2  
  2. {  
  3.       int k;  
  4.       short t;  
  5. };  

       成员k的偏移量为0;成员t的偏移量为4,都不需要调整。但计算出来的大小为6,显然不是成员k大小的整数倍。因此,编译器会在成员t后面补上2个字节,使得结构体的大小变成8从而满足第二个要求。

       由此可见,结构体类型需要考虑到字节对齐的情况,不同的顺序会影响结构体的大小。

       对比下面两种定义顺序:

[cpp] view plain copy
 print?
  1. struct stu3  
  2. {   
  3.        char c1;   
  4.        int i;  
  5.        char c2;  
  6. }  
  7. struct stu4  
  8. {  
  9.        char c1;  
  10.        char c2;  
  11.        int i;  
  12.  }  

       虽然结构体stu3和stu4中成员都一样,但sizeof(struct stu3)的值为12而sizeof(struct stu4)的值为8。

       对于嵌套的结构体,需要将其展开。对结构体求sizeof时,上述两种原则变为:

       (1)展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。

       (2)结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体。

       看下面的例子:

[cpp] view plain copy
 print?
  1. struct stu5  
  2. {  
  3.       short i;  
  4.       struct   
  5.       {  
  6.            char c;  
  7.            int j;  
  8.       } ss;   
  9.       int k;  
  10. }  

       结构体stu5的成员ss.c的偏移量应该是4,而不是2。整个结构体大小应该是16。

      下述代码测试原则2:

[cpp] view plain copy
 print?
  1. struct stu5  
  2. {  
  3.       char i;  
  4.       struct   
  5.       {  
  6.            char c;  
  7.            int j;  
  8.       } ss;   
  9.       char a;  
  10.       char b;  
  11.       char d;  
  12.       char e;  
  13.       char f;  
  14. }  

         结构体ss单独计算占用空间为8,而stu5的sizeof则是20,不是8的整数倍,这说明在计算sizeof(stu5)时,将嵌套的结构体ss展开了,这样stu5中最大的成员为ss.j,占用4个字节,20为4的整数倍。如果将ss当做一个整体,结果应该是24了。


       另一个特殊的例子是结构体中包含数组,其sizeof应当和处理嵌套结构体一样,将其展开,如下例子:

[cpp] view plain copy
 print?
  1. struct ss  
  2. {  
  3.     float f;  
  4.     char p;  
  5.     int adf[3];  
  6. };  
  7. cout<<sizeof(ss)<<endl;  

       其值为20。float占4个字节,到char p时偏移量为4,p占一个字节,到int adf[3]时偏移量为5,扩展为int的整数倍,而非int adf[3]的整数倍,这样偏移量变为8,而不是12。结果是8+12=20,是最大成员float或int的大小的整数倍。


总结以上给中情况下的计算方法,其实可以给出一个通用的简单计算方法,那就是,计算的时候都根据单个基本类型量来计算,如果有嵌套结构体则将嵌套的结构体都展开当成单个元素来处理。展开后,先找出所有成员所占大小的最小公倍数m,比如int的4,然后从第一个成员开始计算,然后把内存划分成一个一个连续的m字节的“槽”,让各个成员去占满这些“槽”,如果字节数小于m的成员直接填充到槽中,填满m字节则开始填充下一个槽,如果还未填满,又来了一个字节数大于当前槽剩余大小的成员,则将该槽剩余的容量滞空,直接跳过然后开始填充下一个槽。

具体来说,比如str1,计算过程为:最大公倍数m=sizeof(int) = 4;所以总的大小为 4+4+4 = 12;对于str2来说:4+4 = 8;str3和str4比较能说明问题,对于str3来说,最大公倍数是4,c1占第一个“槽”中的第一个字节,然而,i是int型占4个字节,第一个槽装不满,所以直接跳过第一个4字节,进入第二个字节,所以 4+4+4 = 12;对于str4:,虽然跟str3成员全部一样,但是前2个char类型变量可以存在第一个槽中,第三个int型成员则跳到第二个槽中,所以是4+4 = 8;比较不一样的是嵌套结构体的计算,比如stu5,嵌套里面的成员都必须从一个新的槽开始装起,所以char型的c和i没装到一起,而是重新进入下一个槽,所以4+4+4+4 = 16;

       如何给结构体变量分配空间由编译器决定,以上情况针对的是Linux下的GCC。在Windows下的VC平台也是这样,至于其他平台,可能会有不同的处理。

文章部分转自 http://blog.csdn.net/szchtx/article/details/8801583,在此基础上做了部分总结和修改

0 0