關於結構體記憶體對齊以及大小端
一直以來,結構體記憶體對齊都是大家討論的熱門話題,特別是對於初學者,總是會感覺理不清楚,本人最開始也是死記硬背,但是可想而知,過一段時間用的時候就會混淆。這幾天又看了幾篇關於記憶體對齊的文章,感覺略有所獲,這裡也分享下我的心得,同時也讓自己加深理解。其實要搞清楚記憶體對齊的問題,有兩個概念要弄清楚。一個就是硬體本身的記憶體分佈,另一個就是結構體變數的記憶體分佈;至於硬體本身的記憶體分佈,我們可以想象成為一格一格的,所有的資料都是存放在一格或者幾格中,在定址讀取資料的時候,每一次讀取N格,因此為了提高記憶體的訪問速度,避免資料的儲存起始地址在某一格記憶體的中端,這也是結構體記憶體對齊的由來。因為雖然編譯器會自動做對齊的動作,但是它也不是萬能的,人為的控制一下有助於優化提高。扯遠了,來看下結構體的記憶體對齊,實際上,我們只要把握住對齊的要點,那就是一定要保證每一個結構體成員變數的起始地址是min(這個變數長度,編譯器對齊位元組)的整數倍,結構體整體的長度是min(所有成員長度的最大值,編譯器對齊位元組)的整數倍。對於巢狀結構體,它的對齊因子並不是這個巢狀結構體的總長,而是這個結構體的對齊因子。哎,文筆太差,直接上兩個例子吧,希望大家諒解。
struct A
{
char c;
int b;
};
struct B
{
char b;
A a;
short s;
};
在4位元組對齊的情況下,sizeof(A)=8,結構體中c的地址位結構體A的首地址,b是int型,長度為4,因此它的儲存起始地址必須是min(4,4)=4的整數倍,這個時候就可以把硬體的記憶體看成是4個位元組一格,c在一格,剩餘3個位元組不足以儲存長度位4的成員b,因此直接補齊3個0,長度就是1+3+4=8了,這個時候8剛好也是4的整數倍,所以不需要補齊。
sizeof(B)=1+3+1+3+4+2+2 = 16,最後那一個2是為了結構體整體大小是4的整數倍而補齊的。
下面繼續討論一點進階的內容,結構體中的變數從前到後是順序從低地址到高地址儲存的,那來看下面的結果:
B obj;
(unsigned int)&obj.a-(unsigned int)&obj.b = 4,這裡的4也就是考察的是填充的長度,因為結構體B的對齊因子是4,如果obj.b的地址是0的話,那麼obj.a的地址就是4,所以結果就是4.
ps,所謂的結構體對齊因子就是結構體中長度最大的成員變數的長度與編譯器對齊位元組的最小值。
哎,越說越不清楚,反正大家記住,要想正確的計算結構體對齊後的長度,首先把每個成員的對齊因子算出來,然後每一個成員的起始地址就是這個因子的整數倍,最後加成出來的總長度再補齊成這個結構體因子的整數倍。
再說一點結合大小端,結構體賦值的知識,一般Windows系統都是小端儲存,所謂小端就是從左到右,地址越來越小,比如一般0x12345678,那麼0x78就是低地址了,那看下面:
這裡為了需要,將A的結構體定義修改為:
struct A
{
char c;
short b;
};
unsigned long long u = 0x12345678;
B* b = (B*)&u;這樣一來,b的首地址就是u的起始地址了,加上小端儲存,所以b->b=0x78,結構體B的成員b後面有一個位元組的補齊,因此0x56被補齊位元組佔據,b->a.c=0x34,b->a.b=0x12;