在用sizeof運(yùn)算符求算某結(jié)構(gòu)體所占空間時(shí)留搔,并不是簡(jiǎn)單地將結(jié)構(gòu)體中所有元素各自占的空間相加,這里涉及到內(nèi)存字節(jié)對(duì)齊的問(wèn)題。從理論上講以现,對(duì)于任何變量的訪問(wèn)都可以從任何地址開(kāi)始訪問(wèn),但是事實(shí)上不是如此约啊,實(shí)際上訪問(wèn)特定類型的變量只能在特定的地址訪問(wèn)邑遏,這就需要各個(gè)變量在空間上按一定的規(guī)則排列,而不是簡(jiǎn)單地順序排列恰矩,這就是內(nèi)存對(duì)齊记盒。
????? 內(nèi)存對(duì)齊的原因:
1)某些平臺(tái)只能在特定的地址處訪問(wèn)特定類型的數(shù)據(jù);
? ? ? 2)提高存取數(shù)據(jù)的速度外傅。比如有的平臺(tái)每次都是從偶地址處讀取數(shù)據(jù)纪吮,對(duì)于一個(gè)int型的變量,若從偶地址單元處存放萎胰,則只需一個(gè)讀取周期即可讀取該變量碾盟;但是若從奇地址單元處存放,則需要2個(gè)讀取周期讀取該變量奥洼。
在C99標(biāo)準(zhǔn)中巷疼,對(duì)于內(nèi)存對(duì)齊的細(xì)節(jié)沒(méi)有作過(guò)多的描述,具體的實(shí)現(xiàn)交由編譯器去處理,所以在不同的編譯環(huán)境下嚼沿,內(nèi)存對(duì)齊可能略有不同估盘,但是對(duì)齊的最基本原則是一致的,對(duì)于結(jié)構(gòu)體的字節(jié)對(duì)齊主要有下面兩點(diǎn):
1)結(jié)構(gòu)體每個(gè)成員相對(duì)結(jié)構(gòu)體首地址的偏移量(offset)是對(duì)齊參數(shù)的整數(shù)倍骡尽,如有需要會(huì)在成員之間填充字節(jié)遣妥。編譯器在為結(jié)構(gòu)體成員開(kāi)辟空間時(shí),首先檢查預(yù)開(kāi)辟空間的地址相對(duì)于結(jié)構(gòu)體首地址的偏移量是否為對(duì)齊參數(shù)的整數(shù)倍攀细,若是箫踩,則存放該成員;若不是谭贪,則填充若干字節(jié)境钟,以達(dá)到整數(shù)倍的要求。
2)結(jié)構(gòu)體變量所占空間的大小是對(duì)齊參數(shù)大小的整數(shù)倍俭识。如有需要會(huì)在最后一個(gè)成員末尾填充若干字節(jié)使得所占空間大小是對(duì)齊參數(shù)大小的整數(shù)倍慨削。
注意:在看這兩條原則之前,先了解一下對(duì)齊參數(shù)這個(gè)概念套媚。對(duì)于每個(gè)變量缚态,它自身有對(duì)齊參數(shù),這個(gè)自身對(duì)齊參數(shù)在不同編譯環(huán)境下不同堤瘤。下面列舉的是兩種最常見(jiàn)的編譯環(huán)境下各種類型變量的自身對(duì)齊參數(shù)
從上面可以發(fā)現(xiàn)玫芦,在windows(32)/VC6.0下各種類型的變量的自身對(duì)齊參數(shù)就是該類型變量所占字節(jié)數(shù)的大小,而在linux(32)/GCC下double類型的變量自身對(duì)齊參數(shù)是4本辐,是因?yàn)閘inux(32)/GCC下如果該類型變量的長(zhǎng)度沒(méi)有超過(guò)CPU的字長(zhǎng)桥帆,則以該類型變量的長(zhǎng)度作為自身對(duì)齊參數(shù),如果該類型變量的長(zhǎng)度超過(guò)CPU字長(zhǎng)师郑,則自身對(duì)齊參數(shù)為CPU字長(zhǎng)环葵,而32位系統(tǒng)其CPU字長(zhǎng)是4,所以linux(32)/GCC下double類型的變量自身對(duì)齊參數(shù)是4宝冕,如果是在Linux(64)下张遭,則double類型的自身對(duì)齊參數(shù)是8。
除了變量的自身對(duì)齊參數(shù)外地梨,還有一個(gè)對(duì)齊參數(shù)菊卷,就是每個(gè)編譯器默認(rèn)的對(duì)齊參數(shù)#pragmapack(n),這個(gè)值可以通過(guò)代碼去設(shè)定宝剖,如果沒(méi)有設(shè)定洁闰,則取系統(tǒng)的默認(rèn)值。在windows(32)/VC6.0下万细,n的取值可以為1扑眉、2、4、8腰素,默認(rèn)情況下為8聘裁。在linux(32)/GCC下,n的取值只能為1弓千、2衡便、4,默認(rèn)情況下為4洋访。注意像DEV-CPP镣陕、MinGW等在windows下n的取值和VC的相同。
了解了這2個(gè)概念之后姻政,可以理解上面2條原則了呆抑。對(duì)于第一條原則,每個(gè)變量相對(duì)于結(jié)構(gòu)體的首地址的偏移量必須是對(duì)齊參數(shù)的整數(shù)倍汁展,這句話中的對(duì)齊參數(shù)是取每個(gè)變量自身對(duì)齊參數(shù)和系統(tǒng)默認(rèn)對(duì)齊參數(shù)#pragma pack(n)中較小的一個(gè)理肺。舉個(gè)簡(jiǎn)單的例子,比如在結(jié)構(gòu)體A中有變量int a善镰,a的自身對(duì)齊參數(shù)為4(環(huán)境為windows/vc),而VC默認(rèn)的對(duì)齊參數(shù)為8年枕,取較小者炫欺,則對(duì)于a,它相對(duì)于結(jié)構(gòu)體A的起始地址的偏移量必須是4的倍數(shù)熏兄。
對(duì)于第二條原則品洛,結(jié)構(gòu)體變量所占空間的大小是對(duì)齊參數(shù)的整數(shù)倍。這句話中的對(duì)齊參數(shù)有點(diǎn)復(fù)雜摩桶,它是取結(jié)構(gòu)體中所有變量的對(duì)齊參數(shù)的最大值和系統(tǒng)默認(rèn)對(duì)齊參數(shù)#pragma pack(n)比較桥状,較小者作為對(duì)齊參數(shù)。舉個(gè)例子假如在結(jié)構(gòu)體A中先后定義了兩個(gè)變量int a;double b;對(duì)于變量a硝清,它的自身對(duì)齊參數(shù)為4辅斟,而#pragma pack(n)值默認(rèn)為8,則a的對(duì)齊參數(shù)為4芦拿;b的自身對(duì)齊參數(shù)為8士飒,而#pragma pack(n)的默認(rèn)值為8,則b的對(duì)齊參數(shù)為8蔗崎。即結(jié)構(gòu)體內(nèi)的每個(gè)遍歷也要取自身的對(duì)齊參數(shù)和默認(rèn)對(duì)齊參數(shù)比較酵幕,取較小者作為這個(gè)變量的對(duì)齊參數(shù)。由于a的最終對(duì)齊參數(shù)為4缓苛,b的最終對(duì)齊參數(shù)為8芳撒,那么兩者較大者是8,然后再拿8和#pragma pack(n)作比較,取較小者作為對(duì)齊參數(shù)笔刹,也就是8芥备,即意味著結(jié)構(gòu)體最終的大小必須能被8整除。
下面是測(cè)試?yán)樱?/p>
注意:以下例子的測(cè)試結(jié)果均在windows(32)/VC下測(cè)試的徘熔,其默認(rèn)對(duì)齊參數(shù)為8?
#include?<iostream>??
using?namespace?std;??
//#pragma?pack(4)????//設(shè)置4字節(jié)對(duì)齊???
//#pragma?pack()?????//取消4字節(jié)對(duì)齊???
typedef?struct?node1??
{??
int?a;??
char?b;??
short?c;??
}S1;??
typedef?struct?node2??
{??
char?a;??
int?b;??
short?c;??
}S2;??
typedef?struct?node3??
{??
int?a;??
short?b;??
static?int?c;??
}S3;??
typedef?struct?node4??
{??
bool?a;??
????S1?s1;??
short?b;??
}S4;??
typedef?struct?node5??
{??
bool?a;??
????S1?s1;??
double?b;??
int?c;??
}S5;??
int?main(int?argc,?char?*argv[])??
{??
cout<
????S1?s1;??
????S2?s2;??
????S3?s3;??
????S4?s4;??
????S5?s5;??
cout<
return?0;??
}?
輸出結(jié)果?:
1?2?4?4?8??
8?12?8?16?32??
下面解釋一下其中的幾個(gè)結(jié)構(gòu)體字節(jié)分配的情況
比如對(duì)于node2
typedef?struct?node2??
{??
????chara;??
????intb;??
????shortc;??
}S2
sizeof(S2)=12;
對(duì)于變量a门躯,它的自身對(duì)齊參數(shù)為1,#pragma pack(n)默認(rèn)值為8酷师,則最終a的對(duì)齊參數(shù)為1讶凉,為其分配1字節(jié)的空間,它相對(duì)于結(jié)構(gòu)體起始地址的偏移量為0山孔,能被4整除懂讯;
對(duì)于變量b,它的自身對(duì)齊參數(shù)為4台颠,#pragma pack(n)默認(rèn)值為8褐望,則最終b的對(duì)齊參數(shù)為4,接下來(lái)的地址相對(duì)于結(jié)構(gòu)體的起始地址的偏移量為1串前,1不能夠整除4瘫里,所以需要在a后面填充3字節(jié)使得偏移量達(dá)到4,然后再為b分配4字節(jié)的空間荡碾;
對(duì)于變量c谨读,它的自身對(duì)齊參數(shù)為2,#pragma pack(n)默認(rèn)值為8坛吁,則最終c的對(duì)齊參數(shù)為2劳殖,而接下來(lái)的地址相對(duì)于結(jié)構(gòu)體的起始地址的偏移量為8,能整除2拨脉,所以直接為c分配2字節(jié)的空間哆姻。
此時(shí)結(jié)構(gòu)體所占的字節(jié)數(shù)為1+3+4+2=10字節(jié)
最后由于a,b玫膀,c的最終對(duì)齊參數(shù)分別為1矛缨,4,2匆骗,最大為4劳景,#pragmapack(n)的默認(rèn)值為8,則結(jié)構(gòu)體變量最后的大小必須能被4整除碉就。而10不能夠整除4盟广,所以需要在后面填充2字節(jié)達(dá)到12字節(jié)。其存儲(chǔ)如下:
|char|----|----|----|??4字節(jié)??
|--------int--------|??4字節(jié)??
|--short--|----|----|??4字節(jié)??
總共占12個(gè)字節(jié)
對(duì)于node3瓮钥,含有靜態(tài)數(shù)據(jù)成員?
typedef?struct?node3??
{??
int?a;??
short?b;??
static?int?c;??
}S3;??
則sizeof(S3)=8.這里結(jié)構(gòu)體中包含靜態(tài)數(shù)據(jù)成員筋量,而靜態(tài)數(shù)據(jù)成員的存放位置與結(jié)構(gòu)體實(shí)例的存儲(chǔ)地址無(wú)關(guān)(注意只有在C++中結(jié)構(gòu)體中才能含有靜態(tài)數(shù)據(jù)成員烹吵,而C中結(jié)構(gòu)體中是不允許含有靜態(tài)數(shù)據(jù)成員的)。其在內(nèi)存中存儲(chǔ)方式如下:
|--------int--------|???4字節(jié)??
|--short-|----|----|????4字節(jié)
而變量c是單獨(dú)存放在靜態(tài)數(shù)據(jù)區(qū)的桨武,因此用siezof計(jì)算其大小時(shí)沒(méi)有將c所占的空間計(jì)算進(jìn)來(lái)肋拔。
而對(duì)于node5,里面含有結(jié)構(gòu)體變量
typedef?struct?node5??
{??
bool?a;??
????S1?s1;??
double?b;??
int?c;??
}S5;??
sizeof(S5)=32呀酸。
對(duì)于變量a凉蜂,其自身對(duì)齊參數(shù)為1,#pragma pack(n)為8性誉,則a的最終對(duì)齊參數(shù)為1窿吩,為它分配1字節(jié)的空間,它相對(duì)于結(jié)構(gòu)體起始地址的偏移量為0错览,能被1整除纫雁;
對(duì)于s1,它的自身對(duì)齊參數(shù)為4(對(duì)于結(jié)構(gòu)體變量倾哺,它的自身對(duì)齊參數(shù)為它里面各個(gè)變量最終對(duì)齊參數(shù)的最大值)轧邪,#pragma pack(n)為8,所以s1的最終對(duì)齊參數(shù)為4羞海,接下來(lái)的地址相對(duì)于結(jié)構(gòu)體起始地址的偏移量為1忌愚,不能被4整除,所以需要在a后面填充3字節(jié)達(dá)到4却邓,為其分配8字節(jié)的空間菜循;
對(duì)于變量b,它的自身對(duì)齊參數(shù)為8申尤,#pragma pack(n)的默認(rèn)值為8,則b的最終對(duì)齊參數(shù)為8衙耕,接下來(lái)的地址相對(duì)于結(jié)構(gòu)體起始地址的偏移量為12昧穿,不能被8整除,所以需要在s1后面填充4字節(jié)達(dá)到16橙喘,再為b分配8字節(jié)的空間时鸵;
對(duì)于變量c,它的自身對(duì)齊參數(shù)為4厅瞎,#pragma pack(n)的默認(rèn)值為8饰潜,則c的最終對(duì)齊參數(shù)為4,接下來(lái)相對(duì)于結(jié)構(gòu)體其實(shí)地址的偏移量為24和簸,能夠被4整除彭雾,所以直接為c分配4字節(jié)的空間。
此時(shí)結(jié)構(gòu)體所占字節(jié)數(shù)為1+3+8+4+8+4=28字節(jié)锁保。
對(duì)于整個(gè)結(jié)構(gòu)體來(lái)說(shuō)薯酝,各個(gè)變量的最終對(duì)齊參數(shù)為1半沽,4,8吴菠,4者填,最大值為8,#pragma pack(n)默認(rèn)值為8做葵,所以最終結(jié)構(gòu)體的大小必須是8的倍數(shù)占哟,因此需要在最后面填充4字節(jié)達(dá)到32字節(jié)。其存儲(chǔ)如下:
|--------bool--------|???4字節(jié)??
|---------s1---------|???8字節(jié)??
|--------------------|????4字節(jié)??
|--------double------|???8字節(jié)??
|----int----|---------|????8字節(jié)??
另外可以顯示地在程序中使用#pragma pack(n)來(lái)設(shè)置系統(tǒng)默認(rèn)的對(duì)齊參數(shù)酿矢,在顯示設(shè)置之后榨乎,則以設(shè)置的值作為標(biāo)準(zhǔn),其它的和上面所講的類似棠涮,就不再贅述了谬哀,讀者可以自行上機(jī)試驗(yàn)一下。如果需要取消設(shè)置严肪,可以用#pragma pack()來(lái)取消史煎。
當(dāng)把系統(tǒng)默認(rèn)對(duì)齊參數(shù)設(shè)置為4時(shí),即#pragma pack(n)
輸出結(jié)果是:
1?2?4?4?8??
8?12?8?16?24 ??
針對(duì) 結(jié)構(gòu)體所占字節(jié)數(shù) 一定要注意對(duì)齊驳糯;
在沒(méi)有pragma pack(n)情況下篇梭,所占字節(jié)數(shù)會(huì)是 類型中最大對(duì)齊參數(shù)的倍數(shù);
在有pragma pack(n)下酝枢,所占字節(jié)數(shù)一定是n值的倍數(shù)恬偷。