Вопрос

Я «играю» с виртуальным наследством в 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?? Может кто-нибудь объяснить свалку?

Спасибо.

Это было полезно?

Решение

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

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

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

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

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

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

Другие советы

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top