VC++中的類的內存分布(上)


0.序

  目前正在學習C++中,對於C++的類及其類的實現原理也挺感興趣。於是打算通過觀察類在內存中的分布更好地理解類的實現。因為其實類的分布是由編譯器決定的,而本次試驗使用的編譯器為VS2015 RC,其編譯環境為VC++,這里感謝@shenzhigang 提醒。所以此處的標題為《VC++中的類的內存分布》。因為博主可能比較懶,所以把這個知識點分作兩次寫。( ╯□╰ )。

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 包含成員函數的類

  這里要先感謝一下@melonstreet 提到的問題,已改正。

  對於類中的成員函數,我們知道,對於所有類,每個成員函數都只有一個副本。函數代碼是存儲在對象空間之外的。如果對同一個類定義了10個對象,這些對象的成員函數對應的是同一個函數代碼段,而不是10個不同的函數代碼段。在這里,關於具體到對象的成員函數是如何調用的,我們有兩種猜想:第一種猜想是每一個對象中都必須開辟一段內存,用來存儲指向類中的成員函數的指針,每次當外部對對象的成員函數進行調用的時候,通過訪問對象空間中的函數指針從而訪問函數。第二種猜想是類中的成員函數是獨立出來的,每個對象中並沒有儲存成員函數的相關信息,而成員函數的調用是通過編譯器在編譯的時候自動幫我們選擇要訪問的函數。為此,我們也要進行一些測試。

 

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; }
};

 

 

 

  

  輸出結果顯示內存仍然不變,說明成員函數在類的內存中並不占空間。

  綜上可以得出結論:對於無繼承的類的成員函數,是獨立出來的,類的內存中並沒有存儲相應的函數信息。對於成員函數的訪問,是通過編譯器完成的。

 

可是,在這里,我們少考慮了一種情況:虛函數的存在。這是一種特例,內存將會為其分配相應空間。在這里先不做討論,且看下篇的具體分析。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM