注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

落叶清风L的博客

努力向前

 
 
 

日志

 
 
 
 

一道关于sizeof计算结构体和联合体大小的问题  

2014-03-29 11:26:03|  分类: linuxC |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
    今天和学长交流了一下sizeof计算结构体和联合体大小的问题。之后感触颇多 。 也是因为自己比较水 , 这篇博会遭喷 , 但是还是写下来留给自己纪念吧。

    看一个比较坑的题目吧 !
01 typedef struct data1 {
02     
03     int a ;
04     double b;
05     union number {
06         
07     int num ;
08     char name ;
09     float score ;
10 };
11 }A ;
12 
13 typedef struct data2 {
14 
15     int a ;
16     double b ;
17     union number2 {
18     
19         int num ;
20         char name ;
21         float score ;
22     };
23 }B ;

        题目说是不同的变量排列顺序,会不同程度的节省和浪费内存空间。然后给出了这段代码。细心的读者稍微计算一下就发现。这样的两种写法压根就是占一样的内存空间。可能有人会问,既然题目都那样说了,结果应该是不一样吧! 可是事实就是那样,这两个结构体申请的变量所占的空间是一样大小的。 下面我们来简单看看 ~

      手边有电脑的读者可以敲一敲 , 编译会报出两个警告! 很多人估计会忽略这个警告,然后爽快的运行,结果出来后自己郁闷。在这里贴一下测试代码。

01 #include <stdio.h>
02
03 typedef struct data1 {
04    
05     int a ;             //int 4字节 , 所以申请完int a后offset由0变为4
06     union number {
07   
08     int num ;           //这里就不用看了 , 这就是个声明, 声明就是说有这个东西          
09     char name ;         //没有申请实际的变量, 不占内存空间的。就和你知道有平行世界这个玩意
10     float score ;       //但是你实际见过么,没有实际的东西,只知道有,那没用的。不占空间的
11    
12};                       //所以说(不知道恰不恰当)这个是不占内存空间的
13     double b;           //double在64位下是8字节 , 所以b申请完了后, offset=4+8=12 ,但是要满足偏移是当前元素大小的整数倍 , 所以在int a 后填充了4字节 , 故offset=12+4 即16字节
14 }A ;                    //结束后offset为16字节, 刚好是最长元素的整数倍, OK可以了不用填充
15 typedef struct data2 {
16
17     int a ;             //int 4字节 , 所以申请完int a后 , offset由0变为4
18     double b ;          //double在64位下是8字节 , 所以b申请完了后, offset=4+8=12 ,但是要满足偏移是当前元素大小的整数倍 , 所以在int a后填充了4字节 , 故offset=12+4 即16字节
19     union number2 {
20                              
21         int num ;      
22         char name ;     //这和上面一样 , 不占内存空间
23         float score ;  
24  };                    
25 }B ;                    //结束后offset为16字节, 刚好是最长元素的整数倍, OK可以了也不用填充
26
27 int main(int argc, char *argv[])
28 {
29     A a ;
30     B b ;
31
32     printf ("a = %lu  b = %lu \n" , sizeof (a)  , sizeof (b) ) ;
33
34     return 0;
35 }
运行结果:
01 [tutu@localhost c_test]$ gcc sizeof_test.c
02 sizeof_test.c:12:2: warning: declaration does not declare anything [enabled by default]
03  };
04   ^
05 sizeof_test.c:24:3: warning: declaration does not declare anything [enabled by
default]
06   };
07    ^
08 [tutu@localhost c_test]$ ./a.out
09 a = 16  b = 16
10 [tutu@localhost c_test]$

        其实注意那个警告的人很容易发现,12行24行编译器报告了两个警告, 意思是“没有声明什么[默认启用]” 。哦 原来这里仅仅是声明了,并没有定义任何变量。 那还占毛内存空间啊。恍然大悟。至于为什么是16和16 大家首先了解一下对齐的问题。
        以GCC(64位)为例 (不同的编译器,可能会有不同,但遵循的原则相同), 首先字节对齐需要遵循3个原则 ;
1.结构体首地址可以被最宽的成员大小所整除 。
2.结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding)。
3. 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字(trailing padding)。

        好了知道这些已经足以应付这道题了 。 我们来稍微计算一下吧 。如上图中注释写的那样。
        这样, 大家应该可以理解这道题坑在哪了吧 。 一是它并没有省任何内存空间 , 二是写了些没有用的话。可能人家就是考这个吧。没事 , 不吐槽了。 好,趁着手热 , 再看看这个题的变形吧 ! 大家自己试试 :

typedef struct data1 {
   
   int a ;
   union number {
       
   int num ;
   char name ;
   float score ;
};
   double b;
}A ;

typedef struct data2 {

   int a ;
   double b ;
   union number2 {
   
       int num ;
       char name ;
       float score ;
   }T;                     //注意哦 ,这里有实际的变量了啊 !!!  union 按照最长的元素计算
}B ;

        有了上面的例子,这个应该很快拿下吧。
        1.int a ;      offset = 4 字节
        2.double b ; offset = 4+8 即12字节 , 但是要满足偏移是当前成员大小的整数倍 , 所以在int a 后填充4字节 offset=12+4
        3.union 按照int或者float长度计算(int  和float一样长) offset = 16 + 4 即20字节 。
        4.最后由于要满足最长成员长度的整数倍, 所以20后面再填充4字节 , 即在float后面 。所以最后offset=20 +4 即 24 字节 。

        呵呵,相信这些对大家来说都是简单的 。
        linux下有一个函数 size_t offsetof(type  , member),在stddef.h这个头文件里。 它可以计算出每个变量的偏移大小 , 举个栗子 可以这样进行输出 :
printf ("%d  %d  \n" , offsetof (A , a) , offsetof (A , b) , sizeof (A));
printf ("%d  %d  %d  \n" , offsetof (B , a) , offsetof (B , b) , offsetof (B , T) , sizeof (B)) ;

       大家可以试试, 由于offset这个函数滞后计算。所以第一个元素的偏移为0 , 到第二个元素的时候实际上显示的是前一个元素到当前元素的偏移量。 所以最后加上了sizeof。

       好了 , 到这里基本上差不多了 。 有兴趣的读者还可以玩玩这个宏命令 #pragma pack(1) 这个是告诉编译器,我要按1字节对齐 。 具体的情况, 大家自己去玩吧 。这不是重点了 。对齐模数一般为4或者8 。
      
       为什么要内存对齐呢 ?引用前辈的话粘在这里 :
       一来简化了处理器与内存之间传输系统的设计。
       二来可以提升读取数据的速度。
       比如这么一种处理器,它每次读写内存的时候都从某个
8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。某些处理器在数据不满足对齐要求的情况下可能会出错,但是IntelIA32架构的处理器则不管数据是否对齐都能正确工作。不过Intel奉劝大家,如果想提升性能,那么所有的程序数据都应该尽可能地对齐。

      这篇博比较水,各路大神路过多包涵。有问题还请大神们不吝指出!渣渣感激不尽 !~!


                                                                     ------引用Linux_Gao的csdn博客
                                                                                         blog.csdn.net/Linux_Gao/article/details/2612885
                                                                     ------引用laoma的博客
                                                                                         www.cnblogs.com/SprLee/archive/2012/12/12/2814092.html
  评论这张
 
阅读(102)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018