結構體的內存佈局依賴於CPU、操做系統、編譯器及編譯時的對齊選項。結構體內部成員的對齊要求,結構體自己的對齊要求。最重要的有三點html
(一)成員對齊。對於結構體內部成員,一般會有這樣的規定:各成員變量存放的起始地址相對於結構的起始地址的偏移量必須爲該變量的類型所佔用的字節數的倍數。可是也能夠看到,有時候某些字段若是嚴格按照大小緊密排列,根本沒法達到這樣的目的,所以有時候必須進行padding。各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節編譯器會自動填充也就是padding。程序員
(二)而後,還要考慮整個結構體的對齊需求。ANSI C標準規定結構體類型的對齊要求不能比它全部字段中要求最嚴格的那個寬鬆,能夠更嚴格。實際上要求結構體至少是其中的那個最大的元素大小的整數倍。由於有時候咱們使用的是結構體數組,因此結構體的大小還得保證結構體數組中各個結構體知足對齊要求,同時獨立的結構體與結構體數組中單個結構體的大小應當是一致的。算法
(三)編譯器的對齊指令。VC 中提供了#pragma pack(n)來設定變量以n字節對齊方式。n字節對齊就是說變量存放的起始地址的偏移量有兩種狀況:第1、若是n大於等於該變量所佔用的字節數,那麼偏移量必須知足默認的對齊方式,第2、若是n小於該變量的類型所佔用的字節數,那麼偏移量爲n的倍數,不用知足默認的對齊方式。結構的總大小也有個約束條件,分下面兩種狀況:若是n大於全部成員變量類型所佔用的字節數,那麼結構的總大小必須爲佔用空間最大的變量佔用的空間數的倍數。數組
規則:
一、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,之後每一個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
二、結構(或聯合)的總體對齊規則:在數據成員完成各自對齊以後,結構(或聯合)自己也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
三、結合一、2推斷:當#pragma pack的n值等於或超過全部數據成員長度的時候,這個n值的大小將不產生任何效果。架構
總結一下:
成員對齊有一個重要的條件,即每一個成員分別對齊.即每一個成員按本身的方式對齊.若是有#pragma pack(8),它雖然指定了按8字節對齊,但並非全部的成員都是以8字節對齊.其對齊的規則是,每一個成員按類型的對齊參數(一般是這個類型的大小)和指定對齊參數(這裏是8字節)中較小的一個對齊.而且結構的長度必須爲所用過的全部對齊參數的整數倍,不夠就補空字節.也就是說對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時能夠保證每一項都邊界對齊。實際上根據這些規則安排整個的內存佈局的算法很簡單,假設起始地址爲0,開始安放第1個成員,而後找到下一個成員能夠安放的起始位置,首先這個位置確定在第一個成員以外,其次知足那些對齊因素。找到知足這兩個條件的第一個位置便可。而後再考慮下一個成員,逐次進行下去。最後再考慮整個結構體的對齊因素,肯定整個結構體的結束位置,這個位置的下個位置也就是下一個結構體的開始位置,保證它可以知足對齊。佈局
好比:post
struct MyStruct { char dda; double dda1; int type }; (簡單說明) struct MyStruct { char dda;//偏移量爲0,知足對齊方式,dda佔用1個字節; double dda1;//下一個可用的地址的偏移量爲1,不是sizeof(double)=8 //的倍數,須要補足7個字節才能使偏移量變爲8(知足對齊 //方式),所以VC自動填充7個字節,dda1存放在偏移量爲8 //的地址上,它佔用8個字節。 int type;//下一個可用的地址的偏移量爲16,是sizeof(int)=4的倍 //數,知足int的對齊方式,因此不須要VC自動填充,type存 //放在偏移量爲16的地址上,它佔用4個字節。 };//全部成員變量都分配了空間,空間總的大小爲1+7+8+4=20,不是結構 //的節邊界數(即結構中佔用最大空間的類型所佔用的字節數sizeof //(double)=8)的倍數,因此須要填充4個字節,以知足結構的大小爲 //sizeof(double)=8的倍數。 因此該結構總的大小爲:sizeof(MyStruc)爲1+7+8+4+4=24。其中總的有7+4=11個字節是VC自動填充的,沒有聽任何有意義的東西。
爲什麼要內存對齊性能
http://www.ibm.com/developerworks/library/pa-dalign/大數據
由於處理器讀寫數據,並非以字節爲單位,而是以塊(2,4,8,16字節)爲單位進行的。若是不進行對齊,那麼原本只須要一次進行的訪問,可能須要好幾回才能完成,而且還要進行額外的merger或者數據分離。致使效率低下。更嚴重地,會由於cpu不容許訪問unaligned address,就會報錯,或者打開調試器或者dump core,好比sun sparc solaris絕對不會容忍你訪問unaligned address,都會以一個core結束你的程序的執行。因此通常編譯器都會在編譯時作相應的優化以保證程序運行時全部數據都是存儲在'aligned address'上的,這就是內存對齊的由來。優化
在'Data alignment: Straighten up and fly right'這篇文章中做者還得出一個結論那就是:"若是訪問的地址是unaligned的,那麼採用大粒度訪問內存有可能比小粒度訪問內存還要慢"。
位域
若是結構體中含有位域(bit-field),那麼VC中準則又要有所更改:
1) 若是相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則後面的字段將緊鄰前一個字段存儲,直到不能容納爲止;
2) 若是相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則後面的字段將重新的存儲單元開始,其偏移量爲其類型大小的整數倍;
3) 若是相鄰的位域字段的類型不一樣,則各編譯器的具體實現有差別,VC6採起不壓縮方式(不一樣位域字段存放在不一樣的位域類型字節中),Dev-C++和GCC都採起壓縮方式;
備註:當兩字段類型不同的時候,對於不壓縮方式,例如:
struct N
{
char c:2;
int i:4;
};
依然要知足不含位域結構體內存對齊準則第2條,i成員相對於結構體首地址的偏移應該是4的整數倍,因此c成員後要填充3個字節,而後再開闢4個字節的空間做爲int型,其中4位用來存放i,因此上面結構體在VC中所佔空間爲8個字節;而對於採用壓縮方式的編譯器來講,遵循不含位域結構體內存對齊準則第2條,不一樣的是,若是填充的3個字節能容納後面成員的位,則壓縮到填充字節中,不能容納,則要單獨開闢空間,因此上面結構體N在GCC或者Dev-C++中所佔空間應該是4個字節。
4) 若是位域字段之間穿插着非位域字段,則不進行壓縮;
備註:
結構體
typedef struct
{
char c:2;
double i;
int c2:4;
}N3;
在GCC下佔據的空間爲16字節,在VC下佔據的空間應該是24個字節。
5) 整個結構體的總大小爲最寬基本類型成員大小的整數倍。
看一段引用
首先,至少有一點能夠確定,那就是ANSI C保證結構體中各字段在內存中出現的位置是隨它們的聲明順序依次遞增的,而且第一個字段的首地址等於整個結構體實例的首地址。這時,有朋友可能會問:"標準是否規定相鄰字段在內存中也相鄰?"。唔,對不起,ANSI C沒有作出保證,你的程序在任什麼時候候都不該該依賴這個假設。那這是否意味着咱們永遠沒法勾勒出一幅更清晰更精確的結構體內存佈局圖?哦,固然不是。不過先讓咱們從這個問題中暫時抽身,關注一下另外一個重要問題————內存對齊。
許多實際的計算機系統對基本類型數據在內存中存放的位置有限制,它們會要求這些數據的首地址的值是某個數k(一般它爲4或8)的倍數,這就是所謂的內存對齊,而這個k則被稱爲該數據類型的對齊模數(alignment modulus)。當一種類型S的對齊模數與另外一種類型T的對齊模數的比值是大於1的整數,咱們就稱類型S的對齊要求比T強(嚴格),而稱T比S弱(寬鬆)。這種強制的要求一來簡化了處理器與內存之間傳輸系統的設計,二來能夠提高讀取數據的速度。好比這麼一種處理器,它每次讀寫內存的時候都從某個8倍數的地址開始,一次讀出或寫入8個字節的數據,假如軟件能保證double類型的數據都從8倍數地址開始,那麼讀或寫一個double類型數據就只須要一次內存操做。不然,咱們就可能須要兩次內存操做才能完成這個動做,由於數據或許剛好橫跨在兩個符合對齊要求的8字節內存塊上。某些處理器在數據不知足對齊要求的狀況下可能會出錯,可是Intel的IA32架構的處理器則無論數據是否對齊都能正確工做。不過Intel奉勸你們,若是想提高性能,那麼全部的程序數據都應該儘量地對齊。Win32平臺下的微軟C編譯器(cl.exe for 80x86)在默認狀況下采用以下的對齊規則: 任何基本數據類型T的對齊模數就是T的大小,即sizeof(T)。好比對於double類型8字節),就要求該類型數據的地址老是8的倍數,而char類型數據(1字節)則能夠從任何一個地址開始。
Linux下的GCC奉行的是另一套規則(在資料中查得,並未驗證,如錯誤請指正):任何2字節大小(包括單字節嗎?)的數據類型(好比short)的對齊模數是2,而其它全部超過2字節的數據類型(好比long,double)都以4爲對齊模數。
如今回到咱們關心的struct上來。ANSI C規定一種結構類型的大小是它全部字段的大小以及字段之間或字段尾部的填充區大小之和。嗯?填充區?對,這就是爲了使結構體字段知足內存對齊要求而額外分配給結構體的空間。那麼結構體自己有什麼對齊要求嗎?有的,ANSI C標準規定結構體類型的對齊要求不能比它全部字段中要求最嚴格的那個寬鬆,能夠更嚴格(但此非強制要求,VC7.1就僅僅是讓它們同樣嚴格)。
在實際開發中,咱們能夠經過指定/Zp編譯選項來更改編譯器的對齊規則。好比指定/Zpn(VC7.1中n能夠是一、二、四、八、16)就是告訴編譯器最大對齊模數是n。在這種狀況下,全部小於等於n字節的基本數據類型的對齊規則與默認的同樣,可是大於n個字節的數據類型的對齊模數被限制爲n。事實上,VC7.1的默認對齊選項就至關於/Zp8。仔細看看MSDN對這個選項的描述,會發現它鄭重告誡了程序員不要在MIPS和Alpha平臺上用/Zp1和/Zp2選項,也不要在16位平臺上指定/Zp4和/Zp8(想一想爲何?)。
##################################################################
參考文獻:
再談內存對齊問題- http://blog.ednchina.com/jasony/92132/message.aspx
也談內存對齊- http://bigwhite.blogbus.com/logs/1347304.html