結構體的內存空間分配及字節對齊

關於內存對齊
一:
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。

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 test1 
{
	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 )


推薦有關結構體字節對齊的好博客:http://www.cnblogs.com/motadou/archive/2009/01/17/1558438.html

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