1. 程式人生 > >C++ 類大小分析

C++ 類大小分析

以下測試程式碼的執行環境:

Ubuntu 16.04.4 LTS

gcc version 4.8.5

x64

  • 空類、單一繼承的空類、多重繼承的空類所佔空間大小為:1(位元組)。

       例項在記憶體中都有一個獨一無二的地址,為了達到這個目的,編譯器往往會給一個空類隱含的加一個位元組,這樣空類在例項化後在記憶體得到了獨一無二的地址

  • 一個類中,虛擬函式本身、成員函式(包括靜態與非靜態)和靜態資料成員都是不佔用類物件的儲存空間。

      類中如果有虛擬函式,那麼該類物件會生成一張虛擬函式表,然後編譯器會生成一個指向該虛表的指標vptr(virtual table pointer),該指標佔用儲存空間,具體下面虛擬函式會講解,靜態資料成員存放的在靜態儲存區,跟類物件儲存空間不一樣。

  • 為了優化存取效率,通常都會遵守記憶體對齊原則。

     通俗的理解是,類中最長的資料成員作為對齊原則,從其內部"最寬基本型別成員"的整數倍地址開始儲存。比如類中有 char、int、float、double等元素,那麼類應該從8的整數倍開始儲存。

例如:

class AA{

public:

    char i;

    char j;

    double k;

};

int main()
{
    cout<<"size = "<<sizeof(AA)<<endl;
    return 0;
}

輸出:16

該類大小等於資料大小之和,由於該類的“最寬”資料型別為double,所以按照8的整數倍進行儲存,這裡的計算方式:1+1+(6)+8 = 16.

註釋:上面計算方法中的“(6)”是記憶體補齊,編譯器自動處理的。之後的計算類似這樣都表示記憶體補齊。

注意,如果資料的擺放順序不一樣,則類的大小也是有區別的:

class AA{

public:

    char i;

    double j;

    char k;

};

int main()
{
    cout<<"size = "<<sizeof(AA)<<endl;
    return 0;
}

輸出:24

類中的資料成員是按照順序進行擺放的,所以計算方式:1+(7)+8+1+(7) = 24

  • 虛擬函式,有虛擬函式的類有個virtual table(虛擬函式表),裡面包含了類的所有虛擬函式,類中有個virtual table pointers,通常成為vptr指向這個virtual table。

一個類裡邊無論一個或者多個虛擬函式,都只有一個vptr,只要有一個虛擬函式,類中肯定會生成一張虛擬函式表vtable,表中儲存的就是每個虛擬函式的地址。vptr指向vtable,然後在vtable中找到對應的虛擬函式。

class AA{
public:
    char i;
    int j;
    int k;
    virtual void func();
    virtual ~AA();
};

輸出:24

注意,這裡也有一個坑,由於測試環境是gcc version 4.8.5,ubuntu 系統 64位,所以虛擬函式指標vptr是8個位元組,如果系統是32位,則是4個位元組。這裡的計算方式:1+4+(3)+4+(4)+8 = 24.

  • 64位和32位,資料型別大小的區別

最大的區別就是指標資料型別64位的大小是8個位元組,32位的是4個位元組

具體請參考:https://blog.csdn.net/puppet_master/article/details/50044965

  • 帶有虛擬函式的單繼承

子類繼承一個帶有虛擬函式的基類,無論子類有沒有虛擬函式,或者有虛擬函式覆蓋的情況,子類也只有一個虛擬函式表。

繼承關係圖如下:參考https://www.cnblogs.com/yanqi0124/p/3829964.html

基類和派生類之間的關係:其中基類的虛擬函式f在派生類中被覆蓋了

 

class AA{
public:
    char i;
    int j;
    int k;
    virtual void func();
    virtual ~AA();
};

class BB : public AA{
public:
    int n;
    virtual void func();
};

 

輸出:AA大小:24,BB大小:24

BB繼承AA的所有資料,其資料成員存放順序如下:

char i; 

int j;

int k;

int n;

計算方法:1+4+(3)+4+4+8(vptr大小) = 24

  • 多繼承,基類如何有虛擬函式,則子類會儲存一個指向基類虛表的指標。

繼承關係圖如下:參考https://www.cnblogs.com/yanqi0124/p/3829964.html

假設基類和派生類之間有如下關係:

對於子類例項中的虛擬函式表,是下面這個樣子:

 

假設,基類和派生類又如下關係:派生類中覆蓋了基類的虛擬函式f

下面是對於子類例項中的虛擬函式表的圖:

從上圖中可以看出,只要繼承的父類有虛擬函式,那麼子類就有一個對應的指向基類虛表的指標vptr。

class AA{
public:
    int i;
    virtual void funcd();
    virtual ~AA();
};

class BB{
public:
    int j;
    virtual void func();
    virtual ~BB();
};

class CC : public AA, public BB{
public:
    int k;
    virtual void func();
    ~CC();
};

輸出:

AA大小:16

BB大小:16

CC大小:32

  • 虛繼承,由於涉及到虛擬函式表和虛基表,會同時增加一個(多重虛繼承下對應多個)vPtr指標指向虛擬函式表vbTable和一個vbPtr指標指向虛基表vbTable。

class AA{
public:
    int i;
    virtual void funcaa();
    virtual ~AA();
};

class BB : virtual public AA{
public:
    int j;
    virtual void funcbb();
    virtual ~BB();
};

class CC : virtual public AA{
public:
    int k;
    virtual void funccc();
    ~CC();
};

class DD : public BB, public CC{
    virtual void funcdd();
    ~DD();
};

 

輸出:

AA大小:16

BB大小:32

CC大小:32

DD大小:48

虛繼承根據編譯器的不同,物件記憶體佈局也不一樣,再次強調,本文章裡邊所有測試環境是GCC編譯器。GCC都是將虛表指標在整個繼承關係中共享的,不共享的是指向虛基類的指標。

對於BB物件記憶體分析:

AA::vptr //虛表指標
AA::i  
BB::vbptr //虛基類指標
BB::vptr //虛表指標
BB::j  

由於虛擬函式指標共享,所以BB最終的大小計算:8(AA::vbptr)+(4+(4))(AA::i)+(4+(4))(BB::j)+8(vptr 共享虛擬函式指標)=32

CC跟BB是類似的計算。

DD的物件記憶體分析:

BB::vptr //虛表指標
BB::vbptr //虛基類指標
BB::j  
CC::vptr //虛表指標
CC::vbptr //虛基類指標
CC::k  
AA::vptr //虛表指標
AA::i  

 

DD大小計算:8(BB::vbptr)+(4+(4))(BB::j)+8(CC::vbptr)+(4+(4))(CC::k)+(4+(4))(AA::i)+8(vptr 共享虛擬函式指標)=48

補充:如果是在VC編譯器上,則虛擬函式指標不共享。