2011-01-05 43 views
7

我在C++中「玩」虛擬繼承,我想知道如何佈置類對象。 我有那些三類:破譯vtable轉儲

class A { 
private: 
    int a; 
public: 
    A() {this->a = 47;} 
    virtual void setInt(int x) {this->a = x;} 
    virtual int getInt() {return this->a;} 
    ~A() {this->a = 0;} 
}; 

class B { 
private: 
    int b; 
public: 
    B() {b = 48;} 
    virtual void setInt(int x) {this->b = x;} 
    virtual int getInt() {return this->b;} 
    ~B() {b = 0;} 
}; 

class C : public A, public B { 
private: 
    int c; 
public: 
    C() {c = 49;} 
    virtual void setInt(int x) {this->c = x;} 
    virtual int getInt() {return this->c;} 
    ~C() {c = 0;} 
}; 

(我認爲他們是正確的:P)

我以前-fdump-class-hierarchy與G ++,我得到這個

Vtable for A 
A::_ZTV1A: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI1A) 
16 A::setInt 
24 A::getInt 

Class A 
    size=16 align=8 
    base size=12 base align=8 
A (0x10209fb60) 0 
    vptr=((& A::_ZTV1A) + 16u) 

Vtable for B 
B::_ZTV1B: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI1B) 
16 B::setInt 
24 B::getInt 

Class B 
    size=16 align=8 
    base size=12 base align=8 
B (0x1020eb230) 0 
    vptr=((& B::_ZTV1B) + 16u) 

Vtable for C 
C::_ZTV1C: 8u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI1C) 
16 C::setInt 
24 C::getInt 
32 (int (*)(...))-0x00000000000000010 
40 (int (*)(...))(& _ZTI1C) 
48 C::_ZThn16_N1C6setIntEi 
56 C::_ZThn16_N1C6getIntEv 

Class C 
    size=32 align=8 
    base size=32 base align=8 
C (0x1020f5080) 0 
    vptr=((& C::_ZTV1C) + 16u) 
    A (0x1020ebd90) 0 
     primary-for C (0x1020f5080) 
    B (0x1020ebe00) 16 
     vptr=((& C::_ZTV1C) + 48u) 

現在到底什麼是那些(int (*)(...))-0x00000000000000010C::_ZThn16_N1C6setIntEi and (int (*)(...))0 ?? 有人可以解釋轉儲嗎?

謝謝。

+1

它的未定義。每個編譯器(甚至編譯器的版本)都會以不同的方式進行操作。 – 2011-01-05 22:15:25

+0

你可以使用C++ filt來解碼'_ZTI1C'其他的位置可能會在編譯器的後續階段被函數指針填充。 – 2011-01-05 22:20:25

回答

6

我不是100%確定這個答案是正確的,但這是我的最佳猜測。

當你有一個繼承多重且非虛擬的類時,類的佈局通常是第一個基類型的完整對象,然後是第二個基類型的完整對象,然後是對象本身的數據。如果你看看B,你可以看到A對象的vtable指針,並且如果你看看C,你可以看到有指向A和B對象的vtable的指針。因爲這些對象是這樣佈局的,這意味着如果你有一個B*指針指向一個C對象,指針實際上不在對象的底部;相反它會指向中間的某個地方。這意味着如果您需要將對象投射到A*,則需要調整B*指針的某個數值以將其跳回到對象的起始位置。爲了做到這一點,編譯器需要對需要跳回到對象開始處的字節數進行編碼。我認爲第一個(int(*)(...))實際上只是您需要查看的對象初始字節的原始數量。如果你會注意到,對於A這個vtable,這個指針是0(因爲A的vtable在對象的起始處,而B vtable也是如此(因爲它也位於對象的起始處)。請注意,C vtable有兩個部分 - 第一部分是A的虛擬表,它的第一個瘋狂條目也是零(因爲如果你在A虛擬表,你不需要做任何調整)。但是,該表的前半部分看起來是B vtable,並且注意到它的第一個條目是十六進制值-0x10。如果查看C對象佈局,您會注意到B vtable指針是16個字節A vtable指針之後。這個-0x10值可能是您需要跳過012的校正偏移量用於返回到對象根目錄的vtable指針。

每個vtable的第二個瘋狂條目似乎是指向vtable本身的指針。請注意,它始終等於vtable對象的地址(比較vtable的名稱和指向的地址)。如果你想做任何類型的運行時類型標識,這將是必要的,因爲通常需要查看vtable的地址(或者至少靠近它的前端)。

最後,爲什麼還有的隱晦命名SETINT和getInt功能在C虛函數表的末尾,我敢肯定那是因爲C類型繼承兩套不同的命名setIntgetInt功能 - 通過A一個和一個到B。如果我不得不猜測,這裏的重點是確保編譯器內部可以區分這兩個虛函數。

希望這會有所幫助!

+1

在第一個數字「-0x10」上,我也認爲這是最終對象內子對象的偏移量。關於爲什麼會出現在這裏......我不太同意你的推理,因爲編譯器在執行轉換時(無論是隱式還是顯式)都可以看到所有的類定義,所以它不是編譯器的提示。然後我想到了其他可能的原因,我唯一能想到的是當通過指向'B'的指針刪除時,編譯器可以得到一個指向開始釋放內存的指針。但我不確定這一點。 – 2011-01-05 22:47:23

+1

在第二個條目中,那些很可能不是指向vtable的指針,而是指向與特定實例關聯的typeinfo對象的指針。請注意具體值:'A :: _ ZTV1A' vs'_ZTI1A',並且'A'對象中的vptr被設置爲'(&A :: _ZTV1A - 16u)'... ...它們不重合。 – 2011-01-05 22:51:08

+0

這些都是非常好的觀點。我很確定你在這兩方面都是正確的。 – templatetypedef 2011-01-05 22:58:03

5

這是你的轉儲到C++ filt的跑:

Vtable for A 
A::vtable for A: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& typeinfo for A) 
16 A::setInt 
24 A::getInt 

Class A 
    size=16 align=8 
    base size=12 base align=8 
A (0x10209fb60) 0 
    vptr=((& A::vtable for A) + 16u) 

Vtable for B 
B::vtable for B: 4u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& typeinfo for B) 
16 B::setInt 
24 B::getInt 

Class B 
    size=16 align=8 
    base size=12 base align=8 
B (0x1020eb230) 0 
    vptr=((& B::vtable for B) + 16u) 

Vtable for C 
C::vtable for C: 8u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& typeinfo for C) 
16 C::setInt 
24 C::getInt 
32 (int (*)(...))-0x00000000000000010 
40 (int (*)(...))(& typeinfo for C) 
48 C::non-virtual thunk to C::setInt(int) 
56 C::non-virtual thunk to C::getInt() 

Class C 
    size=32 align=8 
    base size=32 base align=8 
C (0x1020f5080) 0 
    vptr=((& C::vtable for C) + 16u) 
    A (0x1020ebd90) 0 
     primary-for C (0x1020f5080) 
    B (0x1020ebe00) 16 
     vptr=((& C::vtable for C) + 48u) 

不知道什麼(int (*)(...))-0x00000000000000010(int (*)(...))0是。
C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int)部分是一個「虛函數的優化在多個或虛擬繼承的存在所謂的」所描述的here

+0

+1打我吧:)這是我明確表示的問題的唯一部分 - 如果您同時獲得類轉儲和程序集,那麼它很簡單:將'this'指針偏移16並跳轉到'setInt/getInt'(在每種情況下) – 2011-01-05 23:01:45

+0

@David:如果任何其他部分變得清晰,請將它們添加到答案中。 – 2011-01-06 02:31:49