Yahoo 知識+ 將於 2021 年 5 月 4 日 (美國東岸時間) 停止服務,而 Yahoo 知識+ 網站現已轉為僅限瀏覽模式。其他 Yahoo 資產或服務,或你的 Yahoo 帳戶將不會有任何變更。你可以在此服務中心網頁進一步了解 Yahoo 知識+ 停止服務的事宜,以及了解如何下載你的資料。

物件的記憶體布局

對 C++ 物件一個常見的誤解

那篇文章的作者說"成員函式獨立存在於程式的 code section,不會佔用個別物件的空間",那物件呼叫函式時會怎麼處理呢?thiscall和vtable又是什麼?

更新:

也就是說一般的成員函式等效於呼叫一般函式並隱含傳入自己的位址。

呼叫虛擬函式時物件上有個指向vtable的指標,找到要呼叫的函式並隱含傳入自己的位址。

這麼理解對吧?

但是為什麼

class Test{

public:

void test(){}

virtual void test2(){}

double t;

};

若只有test(),sizeof(test)=1

加入test2(),sizeof(test)=4

加入double t,sizeof(test)=16

2 個解答

評分
  • ?
    Lv 5
    6 年前
    最愛解答

    我試著用程式碼簡單解釋:

    class MyClass

    {

    public:

    int i;

    int float f;

    public:

    void SetValues(int Value)

    {

    this->i = Value;

    f = Value; // The same as this->f=Value;

    }

    };

    這個 class 等於 C 語言的:

    typedef struct MyClass

    {

    int i;

    int f;

    } MyClass;

    如你所見,裡面沒有任何 function 的資料。

    至於那個 class member function 則等同 C 語言的:

    void MyClass_SetValues(MyClass *this, int Value)

    {

    this->i = Value;

    this->f = Value;

    }

    意思就是說,C++ 的成員函式其實是感覺上隸屬於某個 class,實際上他就是一個普通的函式,所以不會佔用物件空間。

    至於 this call,最大的重點就是他偷偷幫你傳入 this 指標做為第一個函式參數(叫作隱含傳入)。函式裡面可以直接使用物件成員,就像那個 i=Value 一樣,這是一個方便的方法,但實際上編譯器會自動幫你改為 this->i=Value。

    不過,若你把函式宣告為 static,那就不會有 this 被傳入了。所以 static 成員函式不需要有實體物件就能呼叫,當然他裡面也沒辦法使用物件成員。

    至於 vtable,他是 Virtual Table 的縮寫,顧名思義和 virtual function 有關,我一樣用程式碼舉例:

    class Myface

    {

    public:

    int i;

    public:

    virtual int Func1() =0;

    virtual int Func2() { return i; }

    };

    class MyClass : public Myface

    {

    public:

    int j;

    public:

    virtual int Func1() { return i; }

    virtual int Func2() { return j; }

    }

    這東西等同於 C 的:

    typedef struct MyVTable

    {

    int(*Func1)(void *this);

    int(*Func2)(void *this);

    } MyVTable;

    // Myface

    static

    typedef struct Myface

    {

    MyVTable *VTable;

    int i;

    } Myface;

    static

    int Myface_Func2_Real(Myface *this)

    {

    return this->i;

    }

    static

    MyVTable Myface_VTable =

    {

    // 這邊示意而已,不一定能編譯通過

    .Func1 = NULL,

    .Func2 = Myface_Func2_Real,

    };

    void Myface_Init(Myface *this)

    {

    ((MyVTable*)this) = &Myface_VTable;

    }

    int Myface_Func1(Myface *this)

    {

    return this->VTable.Func1(this);

    }

    int Myface_Func2(Myface *this)

    {

    return this->VTable.Func2(this);

    }

    // MyClass

    static

    typedef struct MyClass

    {

    Myface Inheri;

    int j;

    } MyClass;

    static

    int MyClass_Func1_Real(MyClass *this)

    {

    return this->j;

    }

    static

    int MyClass_Func2_Real(MyClass *this)

    {

    return this->j;

    }

    static

    MyVTable MyClass_VTable =

    {

    // 這邊示意而已,不一定能編譯通過

    .Func1 = MyClass_Func1_Real,

    .Func2 = MyClass_Func2_Real,

    };

    void MyClass_Init(MyClass *this)

    {

    Myface_Init(&this->Inherit);

    ((MyVTable*)this) = &MyClass_VTable;

    }

    以上所見,vtable 是為了 virtual function 而存在,並且被規定必須被放在物件記憶體分配中的首位。

    由於同一個 Class 產生的不管多少物件實體都使用同一個 vtable,所以一個 class 只有一個 vtable,物件實體只要保有一個指標就可以了。

    2015-03-08 22:55:21 補充:

    > 這麼理解對吧?

    是的。

    > 但是為什麼…

    基本上,C++ 的 class 等同於 C 的 struct,除了 class 多了更多功能,以及預設成員為 private 以外。

    所以當放入一個 double 成員時,物件的大小當然就變 8。

    當放入 virtual function,因為這時物件要放入一個 virtual table 的指標,所以就多了 4 (實際上不一定是 4,而是一個指標的大小,在 64 位元平臺上會變成 8)。

    至於沒有任何成員的時候(這裡不算上一般成員函式,因為他們不佔物件空間),物件大小不就變成 0 了?

    2015-03-08 22:55:33 補充:

    這個狀況在有些地方可能會發生一些意外狀況,比方說 malloc(sizeof(Test)) 出來的東西應該是什麼?

    所以這時候編譯器會強制幫你加一些無用的空間,讓物件實體的大小不是零。

    至於這個數值是由實做定義的,也就是說你不能假定無成員的 class 一定是多大,像我就見過 4 的,而你這裡會跑出 1。

    2015-03-08 22:59:48 補充:

    至於

    加入test2(),sizeof(test)=4

    加入double t,sizeof(test)=16

    為什麼不是 4 + 8 --> 12,而是變成 16?

    這就牽涉到 memory alignment 的議題了!

  • John
    Lv 6
    6 年前

    程式也是一段數據, 只是該數據做為執行命令, 而不是計算用的資料.

    主要意思是, 程式碼數據在記憶體中為共用區塊.

    各物件的實體, 是共用程式區塊;

    而資料所佔的記憶體, 會是個別實體物件獨立自行存放與管理.

    ex:

    Class A{

    int Var1, Var2, Var3;

    void FuncA();

    void FuncB();

    }

    new A => 生成新物件實體.

    每次生成新的物件實體, Var1, Var2, Var3, 都會重新取得記憶空間, 佔用之.

    但是, FuncA, FunB, 則是不額外佔據記憶體空間, 只會有一份, 作為共用.

還有問題嗎?立即提問即可得到解答。