расшифровка дампов vtable
Я "играю" с виртуальным наследованием в 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 ответа:
Я не уверен на 100%, что этот ответ верен, но вот мое лучшее предположение.
Когда у вас есть класс, который наследует multiply и не виртуально, макет класса обычно представляет собой полный объект первого базового типа, затем полный объект второго базового типа, а затем данные для самого объекта. Если вы посмотрите на B, вы увидите указатель vtable для объекта A, а если вы посмотрите на C, вы увидите, что есть указатели в vtable для обоих объектов A и B объекты.
Поскольку объекты расположены таким образом, это означает, что если у вас есть указательB*
, указывающий на объектC
, указатель фактически не будет находиться в основании объекта; скорее он будет указывать где-то посередине. Это означает, что если вам когда-либо понадобится привести объект кA*
, вам нужно будет настроить указательB*
на некоторое количество, чтобы пропустить его обратно к началу объекта. Для этого компилятору нужно где-то закодировать нужное количество байт чтобы вернуться назад, перейдите к началу объекта. Я думаю, что самый первый(int(*)(...))
- это на самом деле просто необработанное количество байтов, которые нужно просмотреть, чтобы добраться до начала объекта. Если вы заметите, дляA
vtable этот указатель равен 0 (так как vtable для A находится в начале объекта, и то же самое верно дляB
vtable (так как он также живет в начале объекта. Однако обратите внимание, что vtableC
состоит из двух частей - первая часть является 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)
представляет собой "оптимизацию вызовов виртуальных функций при наличии множественного или виртуального наследования", как описано здесь.