C++對象模型(一)單繼承

1.無虛函數的單繼承

class A
{
public:
    A():_ai(1){}
    void Afun();
    static void static_fun();
    static int _val;
private:
    int _ai;
};

class B:public A
{
public:
    B():_bi(2){}
    void Bfun();
private:
    int _bi;
};

int main()
{
    A a;
    B b;
    cout<<sizeof(A)<<endl;//4
    cout<<sizeof(B)<<endl;//8
    return 0;
}

上述代碼內存分佈:
這裏寫圖片描述
我們可以調試在內存中看一下:(VS2008調試,本文後續都在此環境)
這裏寫圖片描述

2.有虛函數的單繼承

虛函數是C++多態實現的一種形式,那麼,在虛函數中,編譯器是怎麼知道什麼時候調用合適的函數呢?
編譯器爲每個包含虛函數的類創建一個表(虛函數表/VTABLE)。虛表中存放着類裏面虛函數的地址。在每個虛函數的類中,有一個指針指向虛表,稱爲vpointer(VPTR),這個指針指向虛表。當進行多態調用時,編譯器通過VPTR在虛表中通過函數地址來查找函數代碼,這樣就能正確的調用函數實行動態編譯。看下面代碼:

class A
{
public:
    A():_a(1){}
    virtual void fun1(){};
    virtual void fun2(){};
private:
    int _a;
};

int main()
{
    A a;
    cout<<sizeof(A)<<endl;
    return 0;
}

類A的大小爲8說明了A裏面還有別的東西,在此假設爲VPTR。調試上述代碼 ,查看對象a的內存:
這裏寫圖片描述
可以看到 ,對象a的地址開始4個字節處存放的是VPRT。
把VPRT的那串地址拿出來在內存中look一下,VS2008爲小端存儲
這裏寫圖片描述
那我們不妨通過虛表裏面的函數地址調用一下虛函數:

typedef void (* pfun)();
void PrintVirtual(pfun *_pfun)  
{  
   while(*_pfun)
   {  
       (*_pfun)(); 
       ++_pfun;
   }  
}  
void test()
{
    A a;
    pfun* p = (pfun*)*((int*)(&a));//拿出對象a前4個字節,把它強轉爲函數指針

    PrintVirtual(p);
}

成功的通過虛表調用了對象a裏面的虛函數:
這裏寫圖片描述

清楚了以上的虛函數表,下面研究有虛函數的單繼承,看代碼:

class Base
{
public:
    Base():_b(1){}
    virtual void fun1()
    {
        cout<<"Base::fun1()"<<endl;
    }
    virtual void fun2()
    {
        cout<<"Base::fun2()"<<endl;
    }
private:
    int _b;
};

class Derive:public Base
{
public:
    Derive():_d(2){}
    virtual void fun1()
    {
        cout<<"Derive::fun1()"<<endl;
    }
    virtual void fun3()
    {
        cout<<"Derive::fun3()"<<endl;
    }
    virtual void fun4()
    {
        cout<<"Derive::fun4()"<<endl;
    }
private:
    int _d;
};

int main()
{
    Base b;
    Derive d;
    cout<<sizeof(Base)<<endl;//8
    cout<<sizeof(Derive)<<endl;//12
    return 0;
}

按照前面虛表的知識,上面代碼中的理論上派生類Derive的大小是基類大小+派生類成員大小+派生類VPRT = 8+4+4 = 16,但是上面的結果卻是12,這是因爲派生類並沒有生成自己的虛表。派生類真實的內存佈局爲:
這裏寫圖片描述
調試程序,對Derive類對象d的內存佈局進行分析:
這裏寫圖片描述
用上面的打印函數打印驗證:
這裏寫圖片描述
這裏,編譯器是怎麼做的呢?
先將基類虛表裏的內容拷貝一份,如果派生類對基類中的虛函數進行重寫,使用派生類的虛函數替換相同偏移位置的基類虛函數,如果派生類中增加自己的虛函數,按照其在派生類的聲明次序,放在上述虛函數之後。

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