为什么 vtables 有 sizeof(void*) * 2 字节的0x00填充?



我想这是特定于实现的,但是对于使用 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() {} };

将导致主类CA从类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

最新更新