优化结构体中元素的布局

 下面的代码片段定义了结构体A和B:

  1. struct A  // 结构体A  
  2. {  
  3.       int a;  
  4.       char b;  
  5.       short c;  
  6. };  
  7. struct B  // 结构体B  
  8. {  
  9.       char b;  
  10.       int a;  
  11.       short c;  
  12. }; 

在32位机器上,char、short、int三种类型的大小分别是1、2、4。那么上面两个结构体的大小如何呢?

结构体A中包含了一个4字节的int,一个1字节的char和一个2字节的short,B也一样,所以A、B的大小应该都是4+2+1 = 7字节。但是,实验给出的却是另外的结果:

  1. sizeof(strcut A) = 8, sizeof(struct B) = 12 

其原因还要从字节对齐说起。

现代计算机中内存空间都是按照字节来划分的,从理论上来讲,对变量的访问可以从任何地址开始;但在实际情况中,为了提升存取效率,各类型数据需要按照一定的规则在空间上排列,这使得对某些特定类型的数据只能从某些特定地址开始存取,以空间换取时间,这就是字节对齐。

结构体默认的字节对齐一般满足三个准则:

(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除。

(2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员自身大小的整数倍,如有需要,编译器会在成员之间加上填充字节(Internal Adding)。

(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(Trailing Padding)。

按照这三条规则再去分析结构体A和B,就不会对于上述的结果一脸诧异了。这两个结构体在内存空间中的排列如图1-2所示(灰色网格表示的字节为填充字节)。

 
图1-2 结构体A和B的内存分布

在编程应用中,如果空间紧张,需要考虑节约空间,那么就需要将结构体中的各个变量按照上面的原则进行排列。基本的原则是:把结构体中的变量按照类型大小从小到大依次声明,尽量减少中间的填充字节。

也可以采用保留字节的形式显式地进行字节填充实现对齐,以提高存取效率。其实这就是时间与空间的博弈。如下面的代码片段所示,其中的reserved成员对程序没有什么意义,它只是填补空间以达到字节对齐的目的:

  1. struct A  // 结构体A  
  2. {  
  3.     int a;  
  4.     char b;  
  5.     char reserved; // 保留字节,空间换时间  
  6.     short c;  
  7. };  
  8. struct B  // 结构体B  
  9. {  
  10.     char b;  
  11.     char reserved1[3]; // 保留字节1,空间换时间  
  12.     int a;  
  13.     short c;  
  14.     char reserved2[2]; // 保留字节2,空间换时间  
  15. }; 

 

在某些时候,还可以通过编译器的pack指令调整结构体的对齐方式。#pragma pack的基本用法为:

  1. #pragma pack( n )  
  2. n为字节对齐数,其取值为1、2、4、8、16,默认是8。  
  3. #pragma pack(1)// 设置1字节对齐  
  4. struct A  // 结构体A  
  5. {  
  6.     int a;  
  7.     char b;  
  8.     short c;  
  9. }; 

将结构体A的对齐方式设为1字节对齐,那么A就不再有填充字节了,sizeof(A)的结果即为各元素所占字节之和7。

请记住:

了解结构体中元素的对齐规则,合理地为结构体元素进行布局。这样不仅可以有效地节约空间,还可以提高元素的存取效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章