前言
比如有些平台每次讀都是從偶地址開始,如果一個int型(假設為32位系統)如果存放在偶地址開始的地方,那麼一個讀周期就可以讀出,而如果存放在奇地址開始的地方,就可能會需要2個讀周期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該int資料。資料對齊(Data Alignment) 這個要求可以提高儲存空間系統的效能,減少定址次數,代價是浪費了一些空間。換句話說是用相對廉價的空間換得時間。
資料對齊
在我電腦上的各基本類型的所佔位元組數如下:
有結構體A如下:
1: struct A
2: {
3: int i;
4: double d;
5: char c;
6: short s;
7: };
資料對齊有兩種方式,一種是自然對齊,另一種是強制對齊。
自然對齊
結構體資料自然對齊要滿足的條件:
1. 假設資料的位移量X,資料所屬類型所佔位元組數b,那麼需要滿足 X % b == 0。
2. 假設結構體中所佔總位元組數為N,結構體中占位元組數最大的資料類型(比如結構體A中的double)的位元組數為B。那麼需要滿足 N % B == 0。
現在分析結構體A
int i; int型佔4個位元組,結構體的第一個資料,所以位移量為0(位移量的單位是位元組),能夠整除4,滿足條件一
double d; double 型佔8個位元組,位移量為4,不能夠整除8,不滿足條件一,所以將其位移量後移至8,從而滿足條件一。
char c; char型佔1個位元組,位移量為16,能夠整除1,滿足條件一。
short s; short型佔2個位元組,位移量為17,不能夠整除2,不滿足條件一,所以將其位移量後移至18,從而滿足條件一。
綜上所述: i的位移量是0,d的位移量是8,c的位移量是16,s的位移量是18。
假設struct A的起始地址為Addr,那麼i,d,c,s的記憶體位址是Addr+各自的位移量。
將所有位元組加起來應該是20個位元組,即struct A所佔的記憶體空間應該是(結構體A的起始地址 ~ 結構體A的起始地址+19)。但是不滿足條件2,在struct A中占位元組數最大的資料類型是double佔8個位元組,所以總位元組數為24。如何驗證這一說呢?可以聲明一個結構體數組A a[2];如果struct A真的占位元組數為24,那麼&a[1].i – &a[0].i的值應該等於24。換言之,short s佔了兩個位元組之後還另佔了4個位元組的記憶體位址保持資料的對齊,&a[1].i – &a[0].i = 6。有圖有真相:
觀察到兩點
1.每個資料的記憶體位址都是struct A的起始地址(0x002afbac)+各自的位移量。
2.&a[1].i - &a[0].s = 6從而驗證了上面加粗字的內容為真。
強制對齊
強制對齊需要用到#pragma pack
1: #pragma pack(1)
2:
3: struct B
4: {
5: int i;
6: double d;
7: char c;
8: short s;
9: };
10: #pragma pack()
先不理會#pragma pack的具體功能,只需要知道它有一個參數即可。
結構體的強制對齊要滿足:
1. 假設資料的位移量X,資料所屬類型所佔位元組數b1,#pragma pack(b2)的參數為b2,那麼需滿足 X % ( min ( b1, b2) ) == 0。
2. 假設結構體中所佔總位元組數為N,結構體中占位元組數最大的資料類型(比如結構體A中的double)的位元組數為B1,#pragma pack(b2)的參數為B2,那麼應該滿足
N % ( min( B1, B2) ) == 0。
#pragma pack( n ) 的作用就在條件1,2中體現了,也體現了強制對齊與自然對齊的區別。
現在分析結構體B(與結構體唯一的區別就是加了pack)所佔的總位元組數:
int i;位移量為0,能夠整除min (4, 1)。
double d;位移量為4,能夠整除min (8, 1)
char c;位移量為12,能夠整除min (1, 1)
short s;位移量為13,能夠整除min (2, 1)
所以最後的總位元組數為15。又15能夠整除min (8, 1),滿足條件2,因此最後的總位元組數為15。見真相:
總結
自然對齊其實是有預設的pack,在我的電腦中它的預設值為8(可通過#pragma pack(show)來查詢預設值,啟動並執行時候在警告欄中顯示),也就是本文中的自然對齊與
1: #pragma pack(8)
2:
3: struct A
4: {
5: int i;
6: double d;
7: char c;
8: short s;
9: };
10: #pragma pack()
是等價的。