我想了解更多關於vtables和vpointers的內部工作原理,所以我決定嘗試直接使用一些技巧來訪問vtable。我創建了兩個類Base
和Derv
,每個類都有兩個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調用虛函數?)。
這對於典型的SO問題有點長。你能把這個問題凝聚到核心問題上嗎? (實際上,你的任何帖子是否與最後的問題直接相關?) –
@奧利查爾斯沃斯那麼這對我來說,因爲這就是我對這些問題的看法。第二個問題詢問編譯器是否使用類似的方法來啓用多態,所以我認爲我應該包括我自己的。你會建議另一種媒介,我可以通過它分享這些信息並提出問題嗎? – JorenHeit
你應該在這裏提出問題,只要有足夠的背景讓他們理解(這聽起來不像你需要*任何*這些特定問題的背景)。如果你想分享你發現的東西,你應該建立一個博客... –