0.序
目前正在學習C++中,對於C++的類及其類的實現原理也挺感興趣。於是打算通過觀察類在內存中的分布更好地理解類的實現。因為其實類的分布是由編譯器決定的,而本次試驗使用的編譯器為VS2015 RC,所以此處的標題為《VS中的類的內存分布》。
1.對無繼承類的探索
1.1 空類
我們先一步一步慢慢來,從一個空的類開始。
//空類 class test { };
int main(int argc, char *argv[]) { test ts; cout << sizeof(ts) << endl; return 0; }
結果輸出的是1。
於是我們推測,對於一個空類,內存總會為其分配一個字節的空間。為此,我們可以來驗證一下:
int main(int argc, char *argv[]) { test ts; char ch = '0'; int a1, a2; a1 = (int)(&ts); a2 = (int)(&ts + 1); memcpy(&ts, &ch, 1); cout << sizeof(ts) << endl; return 0; }
可以看到,a1為ts的地址(強制轉化為int),a2為ts的下一個地址,然後我們去內存那裡看一下
結果真的把ch裡面的內容寫入到ts中了。
綜上可以得出一個結論,對於一個空類,編譯器總會為其分配一個字節的內存。
1.2 僅含數據成員的類
首先對於數據成員為一個字節(char)的類,通過上述的測試代碼,結果和空類一樣,編譯器分配了一個字節的空間給了類中的char。這是在我們的預料之內的事。
可是當我們的類設計成含有不同類型的數據結構的時候,結果就不同了:
class test { public: char c; int i; };
程序的輸出結果是8。可以看到,此時類占用內存的空間是8個字節。
這就涉及到“內存對齊”了。所以接下來我們就先來探討一下C++裡的“內存對齊”。
內存對齊:
對於一個類(結構體),編譯器為了提高內存讀取速率以及可移植性,存在一種稱作為“內存對齊”的規則。一般對於內存對齊,編譯器會幫你完成,但是這種工作其實是可以由編程者自己完成的。
C++中,可以使用#pragma pack(n)的預編譯處理進行設置“對齊系數”。(這個對齊系數在VC中一般默認為8。)
為了能夠更好地了解內存中內存對齊的流程,我特地畫了分配空間的流程圖。(僅本人自己理解,如有謬誤請各位大俠指出。)
下面我們通過實例來說明一下內存對齊。(在這裡先只考慮數據成員不為類(結構體)的情況)
class test { public: char c; int i; short s; };
雖然類的內容一樣,但是會因為對齊系數n的不同,內存中的分配也會有所不同。下圖能夠比較形象地說明,其中,紅色表示char型,藍色標識int型,綠色表示short型。圖中的列數是根據min(max(結構體中的數據類型),n)確定的。
(1)例子1:pragma pack(1)
(2)例子2:pragma pack(2)
(3)例子3:pragma pack(4)
可以看到,不同的對齊系數會使內存的分布呈現不同的格局。
討論完內存對齊之後,我們來看一看類中的static成員。
類中的static成員:
我們設計一個這樣的類,類中包括有靜態數據成員。
class test { public:
static int si; char c; int i; short s; };
結果我們發現,該類的大小還是和之前無異。
同時,我們通過cout語句查看類中的static成員地址。
cout << sizeof(ts) << '\n' << (int)&ts.si << '\n' << (int)(&ts) << endl;
結果得出的類的地址和類的static成員的地址相差十萬八千裡。顯而易見,類中的static成員並不是和類儲存在一起的。
綜上可以得到的結論是:類中的成員數據中,僅有非static成員數據才會為其開辟內存空間。
1.3 包含成員函數的類
對於類中的成員函數,我們有兩種猜想:第一種猜想是每一個類中都必須開辟一段內存,用來存儲類中的成員函數,每次外部對類的成員函數進行調用的時候,通過訪問類中的函數指針從而訪問函數。第二種猜想是類中的成員函數是獨立出來的,每個類並沒有儲存成員函數的相關信息,而成員函數的調用是通過編譯器在編譯的時候自動幫我們選擇要訪問的函數。為此,我們也要進行一些測試。
class test { public: static int si; char c; int i; short s; test():c('0'),i(0),s(0) {}; void print(void) { cout << sizeof(test) << '\n' << (int)&si << '\n' << (int)this << endl; } };
輸出結果顯示內存仍然不變,說明成員函數在類的內存中並不占空間。
綜上可以得出結論:對於無繼承的類的成員函數,是獨立出來的,類的內存中並沒有存儲相應的函數信息。對於成員函數的訪問,是通過編譯器完成的。
可是,在這裡,我們少考慮了一種情況:虛函數的存在。這是一種特例,內存將會為其分配相應空間。在這裡先不做討論,且看下篇的具體分析。