为什么 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 C
、A
从位置 0
开始 class C
和 B
从 4
(或 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
我想这是特定于实现的,但是对于使用 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 C
、A
从位置 0
开始 class C
和 B
从 4
(或 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