優化結構體中元素的佈局

 下面的代碼片段定義了結構體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。

請記住:

瞭解結構體中元素的對齊規則,合理地爲結構體元素進行佈局。這樣不僅可以有效地節約空間,還可以提高元素的存取效率。

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