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

Why do vtables have sizeof(void*) * 2 bytes of 0x00 padding?

我想这是特定于实现的,但是对于使用 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.

如果代码是在没有 RTTI 的情况下编译的,type_info 可以是 NULL (0)。

上面的 class_offset 解释了为什么你在那里看到零。这是拥有 class 内的 class 偏移量。 IE。拥有:

class A { virtual meth() {} };
class B { virtual meth() {} };
class C: public A, public B { virtual meth() {} };

将导致主要 class CA 从位置 0 开始 class CB4(或 8)位置开始 class C.

指针在那里,因此您可以从任何 class 指针中找到指向拥有对象的指针。因此对于任何 "main" class 它将始终是 0 但对于 B class 虚拟 table 在 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

在早期的编译器中,偏移量用于在调用方法之前计算指向所属 class 的实际指针。但是因为它减慢了直接在拥有 class 上调用方法的情况,现代编译器宁愿生成存根直接减去偏移量并跳转到方法的主要实现(你可以从方法名称中猜到- 注意 8):

_ZThn8_N1C4methEv:
    subq    , %rdi
    jmp     _ZN1C4methEv