跟我一塊兒學習C++虛函數--第一篇

    咱們知道,虛函數做爲C++實現多態的方式,具備強大的RTTI(RunTime Type Identification)功能。虛函數使用起來比較簡單,可是也很容易出錯。本系列將帶着你一步一步瞭解虛函數的內部實現機制,在掌握原理後,我相信你會對虛函數以及C++自己會有進一步的認識和理解。

    注:本系列全部的關於C++虛函數的探索都是在GCC平臺上進行的。

1、帶有虛函數的對象內存佈局ios

    讓咱們先看一段代碼:
#include <iostream>
using namespace std;

class Test
{
public:
    virtual void f(){cout << "Func:f()" << endl;};

public:
    int a;
};

int main()
{
    cout << sizeof(Test) << endl; //輸出:8
    Test test;
    cout << &test << "\t" << &test.a << endl;//輸出:0xbf91e508      0xbf91e50c
    return 0;
}

    在上面的例子中,咱們定義了一個簡單的包含一個虛函數的類。從輸出咱們能夠看出兩點:
    1)編譯器在包含虛函數的類中插入了虛指針vptr,大小爲4。
    2)對包含虛函數的對象,虛指針被安放在對象的起始位置。以下圖所示:
               
    這個例子咱們只是簡單地介紹了包含虛函數對象的成員內存佈局,下面咱們慢慢深刻看看虛表的結構。仍是從例子入手:
#include <iostream>
using namespace std;

class Test
{
public:
    virtual void f(){cout << "Func:f()" << endl;};
    virtual void g(){cout << "Func:g()" << endl;};
    virtual void h(){cout << "Func:h()" << endl;};
private:
    int a;
};

int main()
{
    Test test;
    typedef void (*Func)(void);//定義個函數指針宏
    cout << "vptbl address:" << (int *)(&test) << endl;//輸出:vptbl address:0xbfc85494
    cout << "func1 address:" << (int *)*(int *)(&test) << endl;//輸出:func1 address:0x8048988

    Func pFunc = (Func)*((int *)*(int *)(&test));
    pFunc();//輸出:Func:f()

    pFunc = (Func)*((int *)*(int *)(&test)+1);
    pFunc();//輸出:Func:g()

    pFunc = (Func)*((int *)*(int *)(&test)+2);
    pFunc();//輸出:Func:h()

    return 0;
}

    從輸出結果咱們能夠看出,咱們能夠直接獲取到虛函數表以及表中每個函數的地址。以下圖所示:
    
    
    這裏對上面的操做進行簡單解釋,主要是指針方面的:
    1)取得虛函數表地址:(int *)(&test)。
       在前面咱們已經討論過,對象的起始地址爲虛指針地址。對對象地址進行int *強制轉換實際上就是獲得虛指針,而虛指針的內容就是虛函數表地址。
    
    2)取得虛函數表中第一個虛函數地址:(int *)*(int *)(&test)。
       取得虛表地址後,再次進行取址便獲得了第一個虛函數地址。
    
    3)調用虛函數表中的函數(Func)*((int *)*(int *)(&test))。
       取得第一個虛函數地址後,進行強制類型轉換,便獲得了函數地址。

    好,到這裏相信你們應該對帶有虛函數的對象的內存佈局有一個較爲清楚的瞭解了吧。下一章咱們討論在繼承狀況下的內存佈局狀況。


參考文獻:
      1.《深度探索C++對象模型》
相關文章
相關標籤/搜索