本文簡單總結了在考慮字節對齊的前提下,計算對象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)成員m,AR(m) = min(n, sizeof(m)),即AR(m)在m的size和對齊參數n之間取較小值;(標量類型指基本的字符、整數、浮點數、指針等。)
(2) 數組成員的AR跟數組中每個元素的AR相同;
(3) 整個結構體的AR取結構體中各個成員的AR的最大值;
(4) 應用以上所有規則之後,整個結構體的size還要進一步填充到結構體AR的整數倍上。
舉個例子:(在32位VC中考慮)
#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個字節上,從而佔據4、5、6、7這四個字節。結果成員a之後有三個字節被“浪費”,淪爲“packing bytes”;
(3) 後面AR(c) = min(4, sizeof(short)) = 2,因此它緊跟在i之後,從第8個字節開始並佔據8、9兩個字節,之前不需要再有填充;
(4) 後面AR(d) = min(4, sizeof(double)) = 4,因此它要被對齊到第12個字節上,並佔據12~19這8個字節;而之前又有兩個字節淪爲“packing bytes”了;
(5) 從以上來看,四個成員所用過的最大AR爲4,即AR(A) = max(AR(a), AR(b), AR(c), AR(d)) = 4。而四個成員斷斷續續地佔用了0~19這20個字節,而20正好也是4的整數倍,因此sizeof(A) = 20。
然後考慮B:
(1) AR(e) = min(4, sizeof(short)) = 2,它被放在B的第0個字節上並佔據0、1兩個字節;
(2) 而AR(f) = AR(f[0]) = AR(A),前面已經計算過,AR(A) = 4,所以f要從字節4開始,即e之後被packing了兩個字節;然後,由於sizeof(f) = 5 * sizeof(A) = 5 * 20 = 100,所以它將佔據4~103這100個字節;
(3) 之後AR(g) = min(4, sizeof(char)) = 1,它不需要對齊,直接放在第104個字節上並佔據這一個字節。
(4) AR(B) = max(AR(e), AR(f), AR(g)) = 4。而三個成員佔據了0~104共105個字節,但105並非4的整數倍,於是末尾需要再填充幾個字節使它成爲4的整數倍,即:sizeof(B) = 108。
綜上所述,之前那個程序將輸出:
20
108
感興趣的朋友可以試着把開頭的
#pragma pack(4)
換成
#pragma pack(8)
並重新推導一下,程序的輸出將變成:
24
136
注意,pack(8)是32位VC的默認設置。另外,VC中另一個影響自定義類型的alignment和size的“__declspec(align(n))”,本文就不予討論了。
順便提一下:這些規則有興趣的話瞭解一下就OK,沒興趣的話完全可以不去了解。實際的工程代碼中除非必需,否則不要寫出對字節對齊有依賴的代碼。尤其在網絡通信程序中,大多數時候都沒必要。不要以爲把一個結構體pack(1)一把然後直接send出去或recv進來效率就很高,網絡收發的效率更大程度上取決於下層的緩衝算法,而非send/recv被調用的次數和方式。爲了使程序更加可移值,還是採用在發送端構造發送緩衝,或者逐成員發送、而接收端逐成員解析的方法比較好。有些處理器不支持pack(1)的對象佈局,或者支持地不好,於是輕者降低內存訪問效率,重者造成錯誤或異常。