对于此代码:
class B1{
public:
virtual void f1() {}
};
class D : public B1 {
public:
void f1() {}
};
int main () {
B1 *b1 = new B1();
D *d = new D();
return 0;
}
编译后,g++ -fdump-class-hierarchy
得到的vtable是:
Vtable for B1
B1::_ZTV2B1: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI2B1)
16 B1::f1
Vtable for D
D::_ZTV1D: 3u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI1D)
16 D::f1
我无法理解像(int()(...))0 *这样的条目对应的内容。当然它意味着类似的东西,它是一个返回int并且接受无限数量的参数的函数,我不明白任何事情。 这个函数指针对应哪个函数?你怎么知道的?我是一台64位机器。
第二个函数指针的末尾有一个地址?这对应于谁?
修改的
我使用的编译器是g ++:
g++ -v
Using built-in specs.
Target: x86_64-suse-linux
Configured with: ../configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man --libdir=/usr/lib64 --libexecdir=/usr/lib64 --enable-languages=c,c++,objc,fortran,obj-c++,java,ada --enable-checking=release --with-gxx-include-dir=/usr/include/c++/4.4 --enable-ssp --disable-libssp --with-bugurl=http://bugs.opensuse.org/ --with-pkgversion='SUSE Linux' --disable-libgcj --disable-libmudflap --with-slibdir=/lib64 --with-system-zlib --enable-__cxa_atexit --enable-libstdcxx-allocator=new --disable-libstdcxx-pch --enable-version-specific-runtime-libs --program-suffix=-4.4 --enable-linux-futex --without-system-libunwind --with-arch-32=i586 --with-tune=generic --build=x86_64-suse-linux
Thread model: posix
*gcc version 4.4.1 [gcc-4_4-branch revision 150839] (SUSE Linux)*
答案 0 :(得分:50)
这些是偏移到顶部(多重继承所需)和typeinfo(RTTI)指针。
来自Itanium ABI (您没有使用Itanium编译器,但他们对此的描述非常好):
偏移到顶部将位移从对象虚拟表指针的对象内的位置保存到对象的顶部,作为ptrdiff_t。它始终存在。偏移量提供了一种从具有虚拟表指针的任何基础子对象中查找对象顶部的方法。特别是对于dynamic_cast,这是必要的 (在完整的对象虚拟表中,因此在其所有主要基本虚拟表中,此偏移的值将为零。[...]
typeinfo指针指向用于RTTI的typeinfo对象。它始终存在。给定类的每个虚拟表中的所有条目必须指向相同的typeinfo对象。 typeinfo相等的正确实现是检查指针相等性,除了指针(直接或间接)到不完整类型。 typeinfo指针是多态类的有效指针,即具有虚函数的类,对于非多态类是零。
更详细地偏移到顶部 (按要求)
假设您有一个派生类D
,派生自基类B1
。当您尝试将D
实例转换为B1
类型时会发生什么?由于B1
对象的函数对D
一无所知,因此D
vtable的一部分也必须是有效的B1
vtable。这很容易 - 只需使D
vtable的开头看起来像B1
vtable,然后添加我们需要的任何其他条目。期望B1
的函数会很高兴,因为他们不会使用vtable的任何部分超出他们对B1
的预期。
但是,如果D
现在也派生自B2
,会发生什么?指向D
vtable的指针不能一个有效的B1
vtable 和一个有效的B2
vtable!编译器通过将单独的B2
vtable附加到组合D/B1
vtable的末尾来解决此问题,并在我们尝试从D
转换为{{{}时手动调整vtable-pointer 1}}。
但是,这会导致一个新问题 - 当我们尝试将 back 从B2
投射到B2
时会发生什么?编译器不能只调整vtable-pointer向前调整指针的量,因为它实际上知道以确保我们给出的D
对象它的类型为B2
!特别是,dynamic_cast<D>()
必须能够判断我们的对象是否为D
类型。为此,它需要访问对象的RTTI,对于那个,它需要知道原始对象的vtable的起始位置。这是偏移到顶部值的目的 - 它给了我们原始对象的vtable开始的偏移量,我们获得了对象的RTTI,而C ++的复仇之神允许我们的作物在另一个季节生长。
This page有一些很好的vtable布局示例(在表1c 下)。请注意,由于使用virtual inheritance,它们会稍微复杂一些,这会为每个子类的vtable添加额外的偏移量。
答案 1 :(得分:3)
也许第一个条目是虚拟析构函数,第二个条目是RTTI支持?但这只是猜测。
答案 2 :(得分:1)
我认为引用安腾 ABI 的答案太繁琐而无法理解。
我认为波鸿鲁尔大学和其他人 (https://www.syssec.ruhr-uni-bochum.de/media/emma/veroeffentlichungen/2019/10/02/ACSAC19-VPS.pdf) 的这篇论文以更友好的方式描述了 RTTI 和 Offset-To-Top。
摘自论文本身:
<块引用>RTTI 持有一个指向有关类的类型信息的指针。除其他外,此类型信息包含类及其基类的名称。但是,RTTI 是可选的,并且经常被编译器忽略。仅在程序员使用时才需要,例如,dynamic_cast 或 type_info。因此,可靠的静态分析不能依赖于这些信息。不包含 RTTI 的类将 RTTI 字段设置为零。
<块引用>当一个类像 C 类那样使用多重继承(因此有一个基 vtable 和一个或多个子 vtable)时,需要偏移到顶部。 Offset-to-Top 指定子 vtable 自己的 vtblptr 和对象开头的基础 vtblptr 之间的距离。在我们的示例中,类 C 的子 vtable 的 vtblptr 位于对象中的偏移量 0x10,而基 vtable 的 vtblptr 位于偏移量 0x0。因此,存储在子vtable C的Offset-to-Top字段中的两者之间的距离是-0x10。如果 vtable 是类的基 vtable 或未使用多重继承,则 Offset-to-Top 为 0。