? 在說計(jì)算方式之前先講講幾個(gè)概念一個(gè)是偏移量還有一個(gè)是內(nèi)存對齊。先說偏移量,百度百科對于它的定義是這樣:把存儲單元的實(shí)際地址與其所在段的段地址之間的距離稱為段內(nèi)偏移,也稱為“有效地址或偏移量”。在結(jié)構(gòu)體里面大概是指結(jié)構(gòu)體變量中成員的地址和結(jié)構(gòu)體變量地址的差。然后再說一下內(nèi)存對齊這個(gè)概念:內(nèi)存中存放基本類型數(shù)據(jù)時(shí),計(jì)算機(jī)的系統(tǒng)會對其位置有限制,系統(tǒng)會要求這些數(shù)據(jù)的首地址的值是某個(gè)數(shù)的倍數(shù),而這個(gè)數(shù)被稱為該數(shù)據(jù)類型的對齊模數(shù)。雖然ANSI C標(biāo)準(zhǔn)中沒有強(qiáng)制規(guī)定相鄰聲明的變量內(nèi)存中要相鄰,但是編譯器會自動(dòng)幫你處理這個(gè)問題,也就是相鄰變量之間可能會填充一些字節(jié)。因此在這個(gè)問題上又有了編譯器的區(qū)別。
? 那我們先來講講結(jié)構(gòu)體變量在微軟的編譯器的對齊吧
? ? ? 1.結(jié)構(gòu)成員的首地址要是其最寬的基本類型成員的整數(shù)倍。編譯器在給結(jié)構(gòu)體分配內(nèi)存的時(shí)候先找到最寬的基本成員,然后再在內(nèi)存中尋找地址,并將這個(gè)最寬的基本數(shù)據(jù)類型的大小作為對齊模數(shù)
? ? ? ?2.結(jié)構(gòu)體每一個(gè)成員相對于首地址的偏移量是成員大小的整數(shù)倍,如果沒有達(dá)到這個(gè)要求,編譯器會自動(dòng)填加字節(jié)。編譯器在為結(jié)構(gòu)體成員開辟內(nèi)存的時(shí)候會先檢查開辟內(nèi)存的首地址與結(jié)構(gòu)體變量的首地址之間的偏移量,如果是成員體的整數(shù)倍那么就存放這個(gè)變量,不然的話就在這個(gè)成員和上一個(gè)成員之間填充字節(jié),以達(dá)到整數(shù)倍的目的
? ? ? ?3.結(jié)構(gòu)體所占的總內(nèi)存大小要是最大成員體大小的整數(shù)倍,如果不是,那么編譯器會在末尾補(bǔ)充字節(jié)。結(jié)構(gòu)體的最后一個(gè)成員,不僅要滿足前兩條原則,最后一條準(zhǔn)則也要滿足。
? ? ? 接下來來看看幾個(gè)例子。
struct std
{
char a;
int i;
float b;
};
這個(gè)結(jié)構(gòu)體在VS 2017下的sizeof的運(yùn)算結(jié)果是12。那么根據(jù)上面的對其規(guī)則我們來對其進(jìn)行計(jì)算。
首先是char a。char大小是1,相對于首地址的偏移量是0,然后是int i。int i的大小是4,相對于首地址的偏移量是1,但是1不是4的整數(shù)倍,所以編譯器會自動(dòng)在char a和int i之間填充字節(jié)字節(jié)。所以in i的偏移量是4。而之后的float b大小4偏移量就是int i的偏移量加上int i的大小故float b的偏移量大小就是8,而8正好是4的倍數(shù)那么就不會有字節(jié)填充。而結(jié)構(gòu)體的大小也就自然是最后8+4=12了
從這里也可以看出結(jié)構(gòu)體大小等于最后一個(gè)成員體的大小加上它的偏移量。
那么我們再來看一個(gè)例子
struct std
{
int i;
int c;
double b;
char a;
};
那么我們再利用之前的算法來對其進(jìn)行運(yùn)算,int i的大小是4偏移量是0,int c 的大小是4偏移量是4,double b的大小是8,偏移量是8。char a的大小是1 ,偏移量是16。那么這個(gè)結(jié)構(gòu)體的變量的大小就是16+1=17嗎?答案肯定不是這樣的。在VS的sizeof的運(yùn)算下這個(gè)的結(jié)果是24,為什是24呢,那么這之前說的最后一條原則就要用上了。結(jié)構(gòu)體的大小確實(shí)是等于最后一個(gè)成員的偏移量加上最后一個(gè)成員的大小,但是如果這個(gè)結(jié)構(gòu)不滿足是結(jié)構(gòu)體中最大成員大小的整數(shù)倍這個(gè)條件那么,編譯器會自動(dòng)在最后填充字節(jié)使其滿足,也就是說,雖然我們計(jì)算出的結(jié)果是17但是17并不是8的倍數(shù),所以編譯器自動(dòng)在最后填充字節(jié)使其成為8的倍數(shù),即自動(dòng)擴(kuò)充成24.、
那我們再來說一下GCC編譯器下的模式。GCC編譯器在Windows環(huán)境下用的會比較少,主要在Linux平臺下使用GCC編譯器就不遵守微軟的編譯器下的一些準(zhǔn)則了,比如之前 說過的對齊模數(shù)。微軟的編譯器下的對齊模數(shù)是結(jié)構(gòu)體成員中最大的大小而在GCC編譯器下對齊模數(shù)最大只能是4。這就意味著對齊模數(shù)只能是1,2,4中的一個(gè)。因此之前講過的在微軟編譯器下的的一些原則會有些不同。之前講過的成員的首地址的偏移量要是成員大小的整數(shù)倍在這里就有點(diǎn)區(qū)別了。在GCC中如果成員大小小于等數(shù)4那么繼續(xù)按照之前的標(biāo)準(zhǔn)就好了,但如果大于4,則結(jié)構(gòu)體每個(gè)成員相對于結(jié)構(gòu)體首地址的偏移量只能按照是4的整數(shù)倍來進(jìn)行判斷是否添加填充。來看一個(gè)簡單的例子。
struct std
{
char b;
double c;
};
在這個(gè)例子中,如果是按照微軟的編譯器的話計(jì)算的結(jié)構(gòu)應(yīng)該是16,但是在GCC編譯器下是12。道理就是之前講的。
? 雖然理論上如此但是我自己在試的時(shí)候發(fā)現(xiàn)上面的運(yùn)算結(jié)果是16,沒錯(cuò)是16而不是12!為什么呢?難道是這個(gè)理論錯(cuò)了么,當(dāng)然不是。我們可以嘗試在GCC下計(jì)算一下sizeof(int *)(是int *而不是int)。你會發(fā)現(xiàn)結(jié)果是8(如果是sizeof(int)那么結(jié)果結(jié)果就是4),輸出結(jié)果是8那就解決了我們的疑惑。64位系統(tǒng)的對其長度默認(rèn)是8而32位的才是4。這就合理解釋了為什么計(jì)算結(jié)果是16而不是12。
對齊模數(shù)的選擇只能是基于基本數(shù)據(jù)類型,所以對于結(jié)構(gòu)體嵌套結(jié)構(gòu)體就不能這么,至于其的計(jì)算方式之后再補(bǔ)充