***************************************************
更多精彩,歡迎進入:http://shop115376623.taobao.com
***************************************************
本文所有內容在建立在一個前提下:使用VC編譯器。著重點在于:VC的內存對齊準則;同樣的數據, 不同的排列有不同的大小,另外在有虛函數或虛擬繼承情況下又有如何影響?
內存對齊?!What?Why?
對于一臺32位的機器來說如何才能發揮它的最佳存取效率呢?當然是每次都讀4字節(32bit)(一個字節8位,一次讀取4個字節,剛好滿負荷讀取), 這樣才可以讓它的bus處于最高效率。實際上它也是這么做的,即使你只需要一個字節,它也是 讀一個機器字長(這兒是32bit)。更重要的是,有的機器在存取或存儲數據的時候它要求數據 必須是對齊的,何謂對齊?它要求數據的地址從4的倍數開始,如若不然,它就報錯。還有的機 器它雖然不報錯,但對于一個類似int變量,假如它橫跨一個邊界的兩端,那么它將要進行兩次 讀取才能獲得這個int值。比方它存儲在地址為2~5的四個字節中,那么要讀取這個int,將要 進行兩次讀取,第一次讀取0~3四個字節,第二次讀取4~7四個字節。但是如果我們把這個整 形的起始地址調整到0,4,8…呢?一次存取就夠了!這種調整就是內存對齊了。我們也可以依次 類推到16位或64位的機器上。
邊界該如何調整
對于32位的機器來說,它當然最渴望它的數據的大小都是4 Byte或者4的倍數Byte,這樣它就能 最有效率的存取數據,當然如果數據小于4Byte,那也是沒問題的。那么編譯器要做的便是盡量滿 足這個要求。
這兩天我斷續對VC做了一些實驗,并總結如下三條準則,你要明白的是這并非來自微軟的官方文 檔,但我自以為這些準則或許不全但應該都是正確的:
- 變量存放的起始位置2應為變量的大小與規定對齊量1中較小者的倍數。例如,假 設規定對齊量為4,那么char(1byte)變量應該存儲在偏移量為1的倍數的地方,而整形變 量(4byte)則是從偏移量為4的倍數的地方,而double(8 byte)也同樣應存儲在偏移量為 4的倍數的地方,為什么不是8?因為規定對齊量默認值為4,而4 < 8。在VC中默認對齊量 為8,而非4。
- 結構體整體的大小也應該對齊,對齊依照規定對齊量與最大數據成員兩者中較小的進行。
- Vptr影響對齊而VbcPoint(Virtual base class pointer)不影響。
一個實例
對于類T:
class T{char c;int i;double d;};
將其sizeof輸出后的大小為16,其內存布局如圖T.變量c從偏移量為0開始存儲,而整形i第一個 符號條件的偏移量為4,double型d的第一個符號條件的為8。整個對象的大小為16,不需要再進 行額外的對齊。
圖T(類T 的內存布局) :
同樣的數據,不同的大小
再看類L,它與T存儲同樣類型的數據,僅僅是順序不同罷了,那么它sizeof輸出的大小是多少呢?
類L:
class L{char c;double d;int i;};
它sizeof后的結果或許會令你大吃一驚,或許不會(如果你有認真讀前面的兩條準則)。L sizeof后的結果是24!同樣是一個int,一個char,一個double卻整整多出了8個字節。這期間 發生了什么?我們依據前面兩條規則來看看。C存儲于0的位置,1~7都不能整除8,所以d存儲 在8~15,16給i正好合適,i存儲在16~19。總共花費了20個字節,抱歉不是8的倍數,還得補 齊4個。現在你可以看看圖L的關于類L的內存布局,再比較一下類L和類T的內存布局。
圖L(類L的布局)
我得出了這樣一條并不權威的結論,因為我還沒聽有人這樣說過:在聲明數據成員的時候,將 最大字節數的變量放在最前面3,切忌不要將大小差距很大的類型交替聲明。
Vptr影響對齊而VbcPoint(Virtual base class pointer)不影響
前面的實例只涉及前兩條準則,現在我們來看看第三條的兩個實例:
class X{char a;}; class Y: virtual public X{};
Y的大小為:a占一個字節,VbcPoint(我稱他為虛基類指針)占四個字節。我們不論a與VbcPoint 的位置如何擺放,如果將VbcPoint等同于一個成員數據來看的話,sizeof(Y)都應該為8.實際上 它是5!就我目前的水平,我只能先將其解釋為VbcPoint不參與對齊。
對于vptr這個問題則不存在:
class X{char a;virtual int vfc(){};}
sizeof(X)的大小確實為8.
關于#pragma pack(n)
用#pragma pack(n)改變規定對齊量試試。
-
規定對齊量:實際上并沒有這么一個名詞,是我為了方便而造出來的。在VC中這個“規定對齊量”會有一個默認值,這個默認值一般為8,我原來一直以為這個值以為是4,至于它為什么為8,我現在還不知道。。我們也可以通過#pargma pack(n)來規定這個值,目前n可以為1,2,4,8,16。??
-
這個起始位置指的是相對于結構體(類)來說的。??
-
此處有一點問題,這個問題由獨酌逸醉提出,他認為將最小的數據放在最前面可能會更好,我們有進行過討論,但可惜的是由于在2011/11/24日數據庫丟失,我只能用備份還原,所以丟失了一些數據,無疑,本文的評論也在其中。不過我對這個問題映像深刻,因為我在寫這篇博客的時候便困惑于到底成員是應該放在之前還是之后,因為這兩種情況我都找不到強有力的理由來支撐它們。后來使我確信從大到小排列好于從小到大排列的理由在于,從大到小排列一般無需成員之間的對齊,唯一的對齊工作是最后進行的整個結構體對齊的工作。毫無疑問的是,這應該是最節省內存的方式。再之后,獨酌提出從小到大可能好些,雖然沒有給出有說服力的理由,但卻使我無比困惑,我當時雖然認為從大到小的排列更有優勢,但卻實在想不出一個實例能使得它優于從小到大排列的。不過最終我擊垮了自己的理由,在繼承狀況下從大到小排列很容易被打破,比方,基類的成員為一個char,繼承類的成員為double,int,char雖然基類和繼承類都是按從大到小的順序排列的,但是繼承類的內存布局最終會使char,double,int,char,此時既不能避免成員對齊,又導致后面的結構體對齊。暫時獲得的最終結果是從小到大排列是更好的一種排列方式。(2011/12/31增補)?