我想这是特定于实现的,但是对于使用 libstdc++ 和 libc++(gcc 或 clang)的 armv7、arm64 和 x86_64 构建,似乎 vtables 在开头总是有 8 个字节(64 位上为 16 个字节)的填充,获取 vtable 通常如下所示:
ldr.w r0, <address of vtable>
adds r0, 0x8
str r0, [r1] ; where r1 is the instance
vtable看起来像这样:
vtable+0x00: 0x00000000
vtable+0x04: 0x00000000
vtable+0x08: 0xfirstfunc
vtable+0x0c: 0xsecondfunc
vtable+0x10: 0xthirdfunc
等。。。
有人知道这是为什么吗?
开头应该只有一个零(大小为 void *)(除非在没有 RTTI 的情况下编译)。它实际上不一定是,但它通常是,我稍后会解释。
(至少源自 gcc)ABI 的 VTable 如下所示:
class_offset
type_info
first_virtual_function
second_virtual_function
etc.
type_info可以NULL
(0
),以防代码在没有RTTI的情况下编译。
上面的class_offset
解释了为什么你在那里看到零。这是所属类中的类偏移量。即具有:
class A { virtual meth() {} };
class B { virtual meth() {} };
class C: public A, public B { virtual meth() {} };
将导致主类C
,A
从类C
中的位置0
开始,B
从类C
中的位置4
(或8
)开始。
指针在那里,因此您可以从任何类指针中找到指向所属对象的指针。因此,对于任何"主"类,它将始终是0
但对于B
类虚拟表C
上下文中有效,它将是-4
或-8
。您实际上需要检查 C 的 VTable(后半部分),因为编译器通常不会单独生成 VTable:
_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