1. 程式人生 > >【程式設計筆記】虛擬函式表

【程式設計筆記】虛擬函式表

如果一個C++類中包含有虛擬函式,C++編譯器在進行編譯時,會通過動態聯編機制,為這個類生成一個“虛擬函式表”。
我們通常把所有的方法都是純虛擬函式的類,叫做:介面類。

class BasicTable
{
public:
    virtual void Function1() = 0;
    virtual void Function2() = 0;
};

對於這個介面類裡面方法的實現,需要編寫一個子類繼承於介面類:

class ChildTable : public BasicTable
{
public:
    void Function1()
    {
        printf("Function1.\n"
); }; void Function2() { printf("Function2.\n"); }; };

我們在呼叫的時候,就會涉及到虛擬函式表:

int _tmain(int argc, _TCHAR* argv[])
{
    BasicTable* pBT = new ChildTable;
    pBT->Function1();
    pBT->Function2();

    delete pBT;
    pBT = NULL;

    return 0;
}

那麼指標pBT具體是怎麼呼叫到子類的Function方法,我們來追蹤下記憶體資訊:
這裡寫圖片描述


指標地址為:0x00429800,我們看下指標這塊記憶體的資訊:
這裡寫圖片描述
其實這塊記憶體的前4位位元組就是虛擬函式表的指標地址,這裡涉及到一個知識點:大端,小端的記憶體儲存位置:
Intel X86體系結構的CPU使用Little Endian(小位元組序)來儲存數字。例如有一個數字為0x11223344,那麼這個資料在記憶體中序列的順序是44 33 22 11,即記憶體的低地址存放這個數字的低位。與此相對應的,還有Big Endia(大位元組序),如PowerPC,所以數字0x11223344,在記憶體中的儲存的序列為 11 22 33 44。
所以這塊記憶體地址正確為:0x01245748,我們看下這塊記憶體的資訊:
這裡寫圖片描述

我們在右邊的資訊,看到Function的方法,並且前4個位元組0x012410a0就是Function1的地址,後4個位元組0x01241140就是Function2的地址,我們確認下是不是:
通過Vs監視窗體可以詳細看到:
這裡寫圖片描述
通過一遍的跟蹤,對虛擬函式表具體的資訊,有了叫深的理解,我們直接模擬虛擬函式表的呼叫,寫份程式碼:

class BasicTable
{
public:
    virtual void Function1() = 0;
    virtual void Function2() = 0;
};

class ChildTable : public BasicTable
{
public:
    void Function1()
    {
        printf("Function1.\n");
    };

    void Function2()
    {
        printf("Function2.\n");
    };
};

int _tmain(int argc, _TCHAR* argv[])
{
    BasicTable* pBT = new ChildTable;
    pBT->Function1();
    pBT->Function2();

    //!<獲取虛擬函式表指標
    LPDWORD VtablePtr = (LPDWORD)(*((LPDWORD)pBT));
    //!<拿到Function方法的指標
    DWORD Fn_0 = *(VtablePtr + 0);
    DWORD Fn_1 = *(VtablePtr + 1);

    //!<函式型別宣告
    typedef void(_stdcall* FUN)();
    FUN _Fun1 = (FUN)Fn_0;
    FUN _Fun2 = (FUN)Fn_1;

    //!<直接呼叫
    _Fun1();
    _Fun2();

    delete pBT;
    pBT = NULL;

    return 0;
}

輸出:

Function1.
Function2.
Function1.
Function2.

結論:含有虛擬函式的C++類,它的物件的this指標所指向的地址即是虛擬函式表指標的所在。