2011-01-05 5 views
7

Я «играю» с виртуальным наследованием на C++, и я хочу знать, как кладется объект класса. У меня есть эти три класса:дешифрование vtable dumps

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;} 
}; 

(я думаю, что они являются правильными: р)

я использовал -fdump-class-hierarchy с г ++, и я получил эту

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 (*)(...))-0x00000000000000010 и C::_ZThn16_N1C6setIntEi and (int (*)(...))0 ?? Может кто-нибудь объяснить свалку?

спасибо.

+1

Не определено. Каждый компилятор (и даже версии компилятора) делает это по-другому. –

+0

Вы можете использовать C++ filt для декодирования '_ZTI1C'. Остальные - это места, которые, вероятно, будут заполнены некоторым более поздним этапом в компиляторе с указателями функций. –

ответ

6

Я не уверен на 100%, что этот ответ верен, но вот мое лучшее предположение.

Если у вас есть класс, который наследует размножение и не виртуально, макет класса обычно является полным объектом первого базового типа, а затем полным объектом второго базового типа, а затем данными для самого объекта , Если вы посмотрите на B, вы можете увидеть указатель vtable для объекта A, и если вы посмотрите на C, вы увидите, что есть указатели на vtable для объектов A и B.

Поскольку объекты выложены таким образом, это означает, что если у вас есть указатель B*, указывающий на объект C, указатель на самом деле не находится у основания объекта; скорее это будет указывать где-то посередине. Это означает, что если вам когда-либо понадобится наложить объект на A*, вам нужно будет отрегулировать указатель B* на некоторое количество, чтобы пропустить его обратно в начало объекта. Для этого компилятор должен закодировать где-то количество байтов, которое вам нужно пропустить, чтобы перейти к началу объекта. Я думаю, что самый первый (int(*)(...)) на самом деле просто необработанное количество байтов, на которое нужно смотреть, чтобы добраться до начала объекта. Если вы заметите, что для A vtable этот указатель равен 0 (поскольку vtable для A находится в начале объекта, и то же самое верно для vtable B (так как он также находится в начале объекта. , обратите внимание на то, что таблица vtable C имеет две части: первая часть - это таблица vtable для A, а ее первая сумасшедшая запись также равна нулю (поскольку, если вы находитесь на vtable, вам не нужно делать никаких настроек). Однако после первой половины этой таблицы появляется то, что кажется B vtable, и обратите внимание, что его первая запись - это шестнадцатеричное значение -0x10. Если вы посмотрите на макет объекта C, вы заметите, что указатель vtable B равен 16 байтам после указателя vtable A. Это значение -0x10 может быть коррективным смещением, которое необходимо пропустить назад по B Указатель vtable для возврата к корню объекта.

Вторая сумасшедшая запись каждого виртуального стола представляется указателем на виртуальную таблицу. Обратите внимание, что он всегда равен адресу объекта vtable (сравните имя vtable и то, что он указывает). Это было бы необходимо, если бы вы хотели выполнить идентификацию типа времени выполнения, поскольку обычно это касается адреса vtable (или, по крайней мере, что-то рядом с ним).

Наконец, как для почему это загадочно названием SetInt и функции GetInt в конце C виртуальные таблицы, я уверен, что это потому, что C тип наследует два различных набора функций, названных setInt и getInt - один через A и один до B.Если бы я должен был догадаться, то это означает, что внутренние компоненты компилятора могут различать две виртуальные функции.

Надеюсь, это поможет!

+1

В первом номере '-0x10' я также думал об этом как о смещении подобъекта в конечном объекте. О том, почему это было бы ... Я не совсем согласен с вашими рассуждениями, поскольку компилятор видит все определения классов, когда он выполняет броски (неявные или явные), поэтому он не является подсказкой для компилятора. Затем я подумал о других возможных причинах, и единственное, о чем я могу думать, это то, что при удалении указателя на «B» компилятор может получить указатель на начало освобождения памяти. Но я не уверен в этом. –

+1

Во вторых, это скорее не указатели на vtable, а указатели на объект typeinfo, связанный с этим конкретным экземпляром. Обратите внимание на конкретные значения: 'A :: _ ZTV1A' vs' _ZTI1A' и что vptr в объекте 'A' имеет значение' (& A :: _ ZTV1A-16u) '... они не совпадают. –

+0

Это оба очень хорошие моменты. Я почти уверен, что вы правильно по обоим пунктам. – templatetypedef

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' (в каждом случае) –

+0

@David: если какие-либо другие части станут ясными, добавьте их в ответ. –