расшифровка vtable дампов - PullRequest
       22

расшифровка vtable дампов

9 голосов
/ 06 января 2011

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

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

Спасибо.

Ответы [ 2 ]

6 голосов
/ 06 января 2011

Вот ваш дамп пробежал через с ++ фильт:

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) представляет собой «оптимизацию вызовов виртуальных функций при наличии множественного или виртуального наследования», как описано здесь .

6 голосов
/ 06 января 2011

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

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

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

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

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

...