2012-06-23 40 views
3

我想了解更多關於vtables和vpointers的內部工作原理,所以我決定嘗試直接使用一些技巧來訪問vtable。我創建了兩個類BaseDerv,每個類都有兩個virtual函數(Derv覆蓋了Base的函數)。vtables和這個指針

class Base 
{ 
    int x; 
    int y; 

    public: 
     Base(int x_, int y_) : x(x_), y(y_) {} 

     virtual void foo() { cout << "Base::foo(): x = " << x << '\n'; }  
     virtual void bar() { cout << "Base::bar(): y = " << y << '\n'; } 
}; 

class Derv: public Base 
{ 
    int x; 
    int y; 

    public: 
     Derv(int x_, int y_) : Base(x_, y_), x(x_), y(y_) {} 

     virtual void foo() { cout << "Derived::foo(): x = " << x << '\n'; } 
     virtual void bar() { cout << "Derived::bar(): y = " << y << '\n'; } 
}; 

現在,編譯器爲每個類添加一個vtable指針,佔用內存中的前4個字節(32位)。由於指針指向另一個大小爲sizeof(size_t)的指針,我通過將對象的地址轉換爲size_t*來訪問此指針。現在可以通過索引vpointer來訪問虛擬函數,並將結果轉換爲適當類型的函數指針。我封裝在一個函數執行以下步驟:

template <typename T> 
void call(T *ptr, size_t num) 
{ 
    typedef void (*FunPtr)(); 

    size_t *vptr = *reinterpret_cast<size_t**>(ptr); 
    FunPtr fun = reinterpret_cast<FunPtr>(vptr[num]); 

    //setThisPtr(ptr);  added later, see below! 
    fun(); 
} 

當memberfunctions之一被稱爲這種方式,例如call(new Base(1, 2), 0)來調用Base :: foo(),很難預測會發生什麼,因爲它們被稱爲沒有this指針。

template <typename T> 
void setThisPtr(T *ptr) 
{ 
    asm (mov %0, %%ecx;" :: "r" (ptr)); 
} 

取消註釋setThisPtr(ptr)線的片段:我通過在ecx寄存器加入少許的模板化功能,知道G ++存儲this終場前(然而,這迫使我與-m32編譯器標誌編譯)解決了這個以上,使得現在有一個工作程序:

int main() 
{ 
    Base* base = new Base(1, 2); 
    Base* derv = new Derv(3, 4); 

    call(base, 0); // "Base::foo(): x = 1" 
    call(base, 1); // "Base::bar(): y = 2" 
    call(derv, 0); // "Derv::foo(): x = 3" 
    call(derv, 1); // "Derv::bar(): y = 4" 
} 

我決定分享這一點,因爲在寫這個小程序的過程中,我獲得了更多的瞭解虛函數表是如何工作的,它可能會幫助別人理解這種材料好一點。 但是我仍然有一些問題:
1.編譯64位二進制文​​件時使用了哪個寄存器(gcc 4.x)來存儲該指針?我嘗試了所有64位寄存器,如下所示:http://developers.sun.com/solaris/articles/asmregs.html
2.何時/如何設置此指針?我懷疑編譯器通過一個對象以類似的方式在每個函數調用中設置了這個指針,就像我剛剛做的那樣。這是多態性實際工作的方式嗎? (通過先設置這個指針,然後從vtable調用虛函數?)。

+0

這對於典型的SO問題有點長。你能把這個問題凝聚到核心問題上嗎? (實際上,你的任何帖子是否與最後的問題直接相關?) –

+0

@奧利查爾斯沃斯那麼這對我來說,因爲這就是我對這些問題的看法。第二個問題詢問編譯器是否使用類似的方法來啓用多態,所以我認爲我應該包括我自己的。你會建議另一種媒介,我可以通過它分享這些信息並提出問題嗎? – JorenHeit

+0

你應該在這裏提出問題,只要有足夠的背景讓他們理解(這聽起來不像你需要*任何*這些特定問題的背景)。如果你想分享你發現的東西,你應該建立一個博客... –

回答

4

在Linux x86_64上,我相信其他類UNIX操作系統,函數調用遵循System V ABI (AMD64),它本身遵循用於C++的IA-64 C++ ABI。根據方法的類型,this指針可以通過第一個參數或第二個參數隱式傳遞(當返回值具有非平凡的複製構造函數或析構函數時,它必須作爲臨時棧存在,而第一個參數隱含地是一個指向該空間的指針);否則,虛擬方法調用相同的功能用C調用(在%rdi%rsi%rdx%rcx%r8%r9,溢出到堆棧整數/指針參數;整數/指針返回在%rax;漂浮在%xmm0 - %xmm7;等等) 。虛擬方法調度的工作原理是在vtable中查找一個指針,然後像調用非虛方法一樣調用它。我不太瞭解Windows x64約定,但我相信它的相似之處在於C++方法調用遵循與C函數調用完全相同的結構(它使用與Linux不同的寄存器),只是隱含了一個this爭論第一。

+0

對於微軟兼容的C++在Don Box的Essential COM的介紹部分解釋了vtable的實現(我聽說過另一本書Inside COM) –