sizeof、pack和alignment

本文簡單總結了在考慮字節對齊的前提下,計算對象size的基本規則。但本文只說結論,不討論初衷和更深奧的原理。有興趣的朋友可以讀讀這個:http://msdn.microsoft.com/en-us/library/aa290049(VS.71).aspx

 

爲方便敘述,我們先定義一個概念:“對齊要求”(Alignment Requirement),並用AR來表示,即編譯器將把一個對象m對齊到AR(m)的整數倍的地址上,比如32位的int變量將被安放到某個從4的整數倍開始的內存地址上。

編譯器在計算一個用戶自定義類型(比如結構體、類、聯合體)的size時,不僅要考慮它每個成員的size,還要考慮字節對齊的要求。基本的規則是這樣的:

假如預處理指令(如VC中的#pragma pack(n))或者編譯器選項(比如cl/Zp選項)指定的pack值爲n:那麼

(1)         對於結構體中的標量類型(scalar type)成員mAR(m) = min(n, sizeof(m)),即AR(m)msize和對齊參數n之間取較小值;(標量類型指基本的字符、整數、浮點數、指針等。)

(2)         數組成員的AR跟數組中每個元素的AR相同;

(3)         整個結構體的AR取結構體中各個成員的AR的最大值;

(4)         應用以上所有規則之後,整個結構體的size還要進一步填充到結構體AR的整數倍上。

 

舉個例子:(在32VC中考慮)

#include <iostream>

using namespace std ;

 

#pragma pack(4)

 

struct A {

  char a;

  int b;

  short c;

  double d;

};

 

struct B {

  short e;

  A f[5];

  char g;

};

 

int main() {

  cout << sizeof(A) << endl;

  cout << sizeof(B) << endl;

  return 0 ;

}

首先考慮A

(1)         由於AR(a) = min(4, sizeof(char)) = 1,不需要對齊,而且它是第一個成員,本來就該放在結構體的第0個字節上;

(2)         AR(b) = min(4, sizeof(int)) = 4,因此它要被對齊到第4個字節上,從而佔據4567這四個字節。結果成員a之後有三個字節被“浪費”,淪爲“packing bytes”;

(3)         後面AR(c) = min(4, sizeof(short)) = 2,因此它緊跟在i之後,從第8個字節開始並佔據89兩個字節,之前不需要再有填充;

(4)         後面AR(d) = min(4, sizeof(double)) = 4,因此它要被對齊到第12個字節上,並佔據12~198個字節;而之前又有兩個字節淪爲“packing bytes”了;

(5)         從以上來看,四個成員所用過的最大AR4,即AR(A) max(AR(a), AR(b), AR(c), AR(d)) = 4。而四個成員斷斷續續地佔用了0~1920個字節,而20正好也是4的整數倍,因此sizeof(A) = 20

 

然後考慮B

(1)         AR(e) = min(4, sizeof(short)) = 2,它被放在B的第0個字節上並佔據01兩個字節;

(2)         AR(f) = AR(f[0]) = AR(A),前面已經計算過,AR(A) = 4,所以f要從字節4開始,即e之後被packing了兩個字節;然後,由於sizeof(f) = 5 * sizeof(A) = 5 * 20 = 100,所以它將佔據4~103100個字節;

(3)         之後AR(g) = min(4, sizeof(char)) = 1,它不需要對齊,直接放在第104個字節上並佔據這一個字節。

(4)         AR(B) max(AR(e), AR(f), AR(g)) = 4。而三個成員佔據了0~104105個字節,但105並非4的整數倍,於是末尾需要再填充幾個字節使它成爲4的整數倍,即:sizeof(B) = 108

 

綜上所述,之前那個程序將輸出:

20

108

 

感興趣的朋友可以試着把開頭的

#pragma pack(4)

換成

#pragma pack(8)

並重新推導一下,程序的輸出將變成:

24

136

注意,pack(8)32VC的默認設置。另外,VC中另一個影響自定義類型的alignmentsize的“__declspec(align(n))”,本文就不予討論了。

 

順便提一下:這些規則有興趣的話瞭解一下就OK,沒興趣的話完全可以不去了解。實際的工程代碼中除非必需,否則不要寫出對字節對齊有依賴的代碼。尤其在網絡通信程序中,大多數時候都沒必要。不要以爲把一個結構體pack(1)一把然後直接send出去或recv進來效率就很高,網絡收發的效率更大程度上取決於下層的緩衝算法,而非send/recv被調用的次數和方式。爲了使程序更加可移值,還是採用在發送端構造發送緩衝,或者逐成員發送、而接收端逐成員解析的方法比較好。有些處理器不支持pack(1)的對象佈局,或者支持地不好,於是輕者降低內存訪問效率,重者造成錯誤或異常。

 

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