關於內存對齊
一:
1.什么是內存對齊
假設我們同時聲明兩個變量:
char a;
short b;
用&(取地址符號)觀察變量a,
b的地址的話,我們會發現(以16位CPU為例):
如果a的地址是0x0000,那么b的地址將會是0x0002或者是0x0004。
那么就出現這樣一個問題:0x0001這個地址沒有被使用,那它干什么去了?答案就是它確實沒被使用。因為CPU每次都是從以2字節(16位CPU)或是4字節(32位CPU)的整數倍的內存地址中讀進數據的。如果變量b的地址是0x0001的話,那么CPU就需要先從0x0000中讀取一個short,取它的高8位放入b的低8位,然后再從0x0002中讀取下一個short,取它的低8位放入b的高8位中,這樣的話,為了獲得b的值,CPU需要進行了兩次讀操作。
但是如果b的地址為0x0002,
那么CPU只需一次讀操作就可以獲得b的值了。所以編譯器為了優化代碼,往往會根據變量的大小,將其指定到合適的位置,即稱為內存對齊(對變量b做內存對齊,a、b之間的內存被浪費,a並未多占內存)。
2.結構體內存對齊規則
結構體所占用的內存與其成員在結構體中的聲明順序有關,其成員的內存對齊規則如下:
(1)每個成員分別按自己的對齊字節數和PPB(指定的對齊字節數,32位機默認為4)兩個字節數最小的那個對齊,這樣可以最小化長度。
(2)復雜類型(如結構)的默認對齊方式是它最長的成員的對齊方式,這樣在成員是復雜類型時,可以最小化長度。
(3)結構體對齊后的長度必須是成員中最大的對齊參數(PPB)的整數倍,這樣在處理數組時可以保證每一項都邊界對齊。
(4)計算結構體的內存大小時,應該列出每個成員的偏移地址,則其長度=最后一個成員的偏移地址+最后一個成員數的長度+最后一個成員的調整參數(考慮PPB)。
下面舉例說明上述規則:
#include #pragma pack(2) //指定PPB為2 struct T{ char a; //偏移地址0 int b; //偏移地址2 char c; //偏移地址6 }; #pragma pack() //恢復原來默認PPB,32位下為4 int main(int argc,char * argv[]) { printf("sizeof(struct T)); return 0; }
最后輸出的結果為:8。語句#pragma pack(2)的作用是指定結構體按2字節對齊,即PPB=2。分析如下:
變量a默認為1字節,PB=2,所以a按1字節對齊,a的偏移地址為0。
變量b默認為4字節(在32位機器中int為4字節),PB=2,所以b按2字節對齊,b的偏移地址為2。
變量c默認為1字節,PB=2,所以c按1字節對齊,偏移地址為6。
此時結構體的計算出的字節數為7個字節。最后按規則3,結構體對齊后的字節數為8。sizeof(T)=6+1+1=8
3.范例
(1)#pragma pack(2) //指定PPB為2
struct T{ char a; //偏移地址0 char b; //偏移地址1 int c; //偏移地址2 };
則sizeof(T)=最后一個成員的偏移地址+最后一個成員數的長度=2+4=6。
(2)
struct T1{ char a; //偏移地址0 char b; //偏移地址1 int c; //偏移地址4 }; struct T2{ char a; //偏移地址0 int b; //偏移地址4 char c; //偏移地址8 };
PPB=4,則sizeof(T1)=4+4=8;sizeof(T2)=8+1=9,9不能整除4,故調整數為3,即sizeof(T2)=8+1+3=12
4.注意的問題
(1)字節對齊取決於編譯器;
(2)一定要注意PPB大小,PPB大小由pragam pack(n)指定;
(3)結構體占用的字節數要能被PPB整除。
二:
(1)sizeof也可以對一個函數調用求值,其結果是函數返回類型的大小,函數並不會被調用。
(2)終於搞懂struct結構體內存分配問題了,結構體中各個成員字節對齊遵循以下幾個原則: 直接用下面幾個原則即可判斷結構體的大小
1.結構體每個成員相對於結構體首地址的偏移量(offset)都是(這個)成員大小的整數倍,如有需要編譯器會在成員之間加上填充字節(internaladding);
例如有以下一個結構體
structex { int i; char t; int n; }
第1個成員偏移量為0,是int型成員大小4(假設這太機器的整型長度占4個字節)的整數倍。
第2個成員t為char型,他的大小為1,首先假設在成員i和t之間沒有填充字節,由於i是整型,占4個字節那么在沒有填充之前,第2個成員t相對於結構體的偏移量為4,他是t成員大小1的4倍,符合此條件,所以系統在給結構體第2個成員分配內存時,不會在i和t之間填充字節以到達對齊的目的。
當分配結構體第3個成員n時,首先發現是一個整型數據,大小為4,沒有填充之前,n相對於結構體首地址偏移量為:前面2個成員+填充字節=5,所以當系統發現5不是4(成員大小)的整數倍時,會在成員t之后(或者說n之前)填充3個字節,以使n的偏移量到達8而成為4的整數倍。這樣這個結構體占用內存情況暫時為4+1+3+4。
2.結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之后加上填充字節(trailingpadding)。
上面的結構體內存分配以后還要看是否滿足此條件,假設在最末一個成員之后不需填充字節數,那么這個結構體的大小為12。而ex結構體中最寬基本類型成員為int,大小為4,12為4的整數倍,所以無須再在最末一個成員之后加上填充字節了。所以sizeof(ex)=12;
如果一個結構體如下所示
struc tex1{ int i; char t; int n; char add; }
那么sizeof(ex1)=16;原因就是在最后一個成員之后填充了3個字節。
3.還有一個額外的條件:結構體變量的首地址能夠被其最寬基本類型成員的大小所整除;
4.對於結構體成員屬性中包含結構體變量的復合型結構體再確定最寬基本類型成員時,應當包括復合類型成員的子成員。但在確定復合類型成員的偏移位置時則是將復合類型作為整體看待。
5總結出一個公式:結構體的大小等於最后一個成員的偏移量加上其大小再加上末尾的填充字節數目,即:
sizeof( struct ) = offsetof( last item ) + sizeof( last item ) +sizeof( trailing padding )