C++ 虛函數內存分配

    本文重點參考了《C++ 虛函數表解析》一文(鏈接:http://blog.csdn.net/haoel/article/details/1948051/),陳皓前輩此文講解清晰,之前閱讀受益匪淺,只是代碼中存在一些問題。例如涉及到本文重點虛函數表的地方,寫到

Base b;
cout << "虛函數表地址:" << (int*)(&b) << endl;

    但是,實際上(int*)(&b)並非虛函數表地址,而是對象b的地址,*(int*)(&b)纔是虛函數表的地址。此外,後文中一些指針操作也有異常之處(也有可能是編譯環境不同?)。爲此,我重新進行編碼實驗,並記錄此文。


一、實驗環境

本機操作系統Linux wm-ThinkPad-X240s 3.13.0-44-generic #73-Ubuntu SMP Tue Dec 16 00:22:43UTC 2014 x86_64 x86_64 x86_64 GNU/Linux。注意是64位機器,意味着指針長度爲8字節;如果是32位機器,則指針長度爲4字節。


    我們根據包含虛函數的類是否涉及繼承、如何繼承,分單個類無繼承、一般繼承、多重繼承三種情況,討論虛函數的內存分配。

二、單個類無繼承

        我們編寫下列代碼,其中類Base包含三個虛函數。

#include <iostream>
 
using namespace std;
 
class Base {
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};
 
typedef void (*Fun) ();
 
int main() {
    Base b;
    Fun pFun = NULL;
 
    cout << "sizeof(b): " << sizeof(b) << endl;
 
    cout << "對象的地址: " << (&b) << endl;
    cout << "虛函數表的地址: " << *(int*)(&b) << endl;
 
    pFun = (Fun)(*(int*)(*(int*)(&b)));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&b) + 8));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&b) + 16));
    pFun();
 
    return 0;
}


運行結果爲

wKiom1XVV3SjC2nlAADKd3l7ftA359.jpg

根據運行結果可知,對象b的內存規模爲8字節,這8字節爲存儲虛函數表指針所佔用(64位機器,指針8字節)。

這裏順便說一些C++類中與虛函數無直接關係的內存分配知識,如果沒有虛函數,則類和對象的所佔內存爲類中數據成員的內存量(需要考慮對齊),類中函數成員不佔內存量。那麼如果一個類沒有數據成員和虛函數,對其求sizeof,結果應當是多少呢?結果本來應當是0,但是一個實例它必須在內存中佔有一定的空間,因此實際結果爲1。《劍指offer》中就有類似的題目,有興趣的朋友可以自行實驗。

根據代碼及其運行結果,我們可以推斷出其內存分配如下圖所示。

wKioL1XVXJeCUhG7AAHEaKD_Jy4088.jpg


三、一般繼承

        我們編寫下列代碼,其中Base爲基類, Derive繼承Base類。

#include <iostream>
 
using namespace std;
 
class Base {
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};
 
class Derive : public Base {
public:
    void f() { cout << "Derive::f" << endl; }
    virtual void g1() { cout << "Derive::g1" << endl; }
    virtual void h1() { cout << "Derive::h1" << endl; }
};
 
typedef void (*Fun) ();
 
int main() {
    Derive d;
    Fun pFun = NULL;
 
    cout << "sizeof(d): " << sizeof(d) << endl;
    cout << "對象的地址: " << (&d) << endl;
    cout << "虛函數表的地址: " << *(int*)(&d) << endl;
 
    pFun = (Fun)(*(int*)(*(int*)(&d)));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 8));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 16));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 24));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 32));
    pFun();
 
    return 0;
}

運行結果爲

wKiom1XVWvyAbppoAADoBPPPuzQ059.jpg

根據代碼及其運行結果,我們可以推斷出其內存分配如下圖所示。

wKioL1XVXSDSpDtpAAG4RRuf_BQ431.jpg


四、多重繼承

        我們編寫下列代碼,類Derive繼承類Base1Base2Base3

#include <iostream>
 
using namespace std;
 
class Base1 {
public:
    virtual void f() { cout << "Base1::f" << endl; }
    virtual void g() { cout << "Base1::g" << endl; }
    virtual void h() { cout << "Base1::h" << endl; }
};
 
class Base2 {
public:
    virtual void f() { cout << "Base2::f" << endl; }
    virtual void g() { cout << "Base2::g" << endl; }
    virtual void h() { cout << "Base2::h" << endl; }
};
 
class Base3 {
public:
    virtual void f() { cout << "Base3::f" << endl; }
    virtual void g() { cout << "Base3::g" << endl; }
    virtual void h() { cout << "Base3::h" << endl; }
};
 
class Derive : public Base1, public Base2, public Base3 {
public:
    void f() { cout << "Derive::f" << endl; }
    virtual void g1() { cout << "Derive::g1" << endl; }
    virtual void h1() { cout << "Derive::h1" << endl; }
};
 
typedef void(*Fun)(void);
 
int main() {
    Derive d;
    Fun pFun = NULL;
    int** pVtab = (int**)&d;
 
    cout << "sizeof(b): " << sizeof(d) << endl;
 
    pFun = (Fun)pVtab[0][0];
    pFun();
 
    pFun = (Fun)pVtab[0][2];
    pFun();
 
    pFun = (Fun)pVtab[0][4];
    pFun();
 
    pFun = (Fun)pVtab[0][6];
    pFun();
 
    pFun = (Fun)pVtab[0][8];
    pFun();
 
    pFun = (Fun)pVtab[1][0];
    pFun();
 
    pFun = (Fun)pVtab[1][2];
    pFun();
 
    pFun = (Fun)pVtab[1][4];
    pFun();
 
    pFun = (Fun)pVtab[2][0];
    pFun();
 
    pFun = (Fun)pVtab[2][2];
    pFun();
 
    pFun = (Fun)pVtab[2][4];
    pFun();
 
    return 0;
}

運行結果

wKiom1XVW17icx1QAADnJINvDxs178.jpg

根據代碼及其運行結果,我們可以推斷出其內存分配如下圖所示。

wKioL1XVXfDDdf3VAALywYTDdCA501.jpg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章