Ответ 1
В начале должен быть только один нуль (размер void *) (если не скомпилирован без RTTI). Это на самом деле не обязательно, но обычно это я расскажу позже.
VTable для (по крайней мере, gcc) ABI выглядит следующим образом:
class_offset
type_info
first_virtual_function
second_virtual_function
etc.
Тип_info может быть NULL
(0
), если код был скомпилирован без RTTI.
class_offset
сверху объясняет, почему вы видите нуль. Это смещение класса внутри класса-владельца. То есть имея:
class A { virtual meth() {} };
class B { virtual meth() {} };
class C: public A, public B { virtual meth() {} };
приведет к основному классу C
, A
, начиная с позиции 0
в классе C
и B
, начиная с позиции 4
(или 8
) в классе C
.
Указатель находится там, поэтому вы можете найти из любого указателя класса указатель на объект-владелец. Поэтому для любого "основного" класса он всегда будет 0
, но для виртуальной таблицы класса B
, действительной в контексте C
, это будет -4
или -8
. Вам действительно нужно проверить VTable для C (вторая половина), поскольку компилятор обычно не генерирует VTables отдельно:
_ZTV1C:
// VTable for C and A within C
.quad 0
.quad _ZTI1C
.quad _ZN1CD1Ev
.quad _ZN1CD0Ev
.quad _ZN1C4methEv
// VTable for B within C
.quad -8
.quad _ZTI1C
.quad _ZThn8_N1CD1Ev
.quad _ZThn8_N1CD0Ev
.quad _ZThn8_N1C4methEv
В предыдущих компиляторах смещение использовалось для вычисления реального указателя на собственный класс перед вызовом метода. Но поскольку это замедляет случаи при вызове метода непосредственно на собственном классе, современные компиляторы скорее генерируют заглушку, которая вычитает смещение напрямую и переходит к основной реализации метода (как вы можете догадаться из имени метода - обратите внимание на 8
):
_ZThn8_N1C4methEv:
subq $8, %rdi
jmp _ZN1C4methEv