52

对于此代码:

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;
}

编译后,我得到的 vtableg++ -fdump-class-hierarchy是:

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)*
4

3 回答 3

55

这些是偏移到顶部(多重继承所需)和类型信息(RTTI)指针。

来自Itanium ABI (您没有使用 Itanium 编译器,但他们对此的描述非常好)

到顶部的偏移量保存从指向该虚拟表的虚拟表指针的对象内的位置到对象顶部的位移,作为 ptrdiff_t。它总是存在的。偏移量提供了一种使用虚拟表指针从任何基础子对象中查找对象顶部的方法。这对于 dynamic_cast 特别是必要的。
(在完整的对象虚拟表中,因此在其所有主要基本虚拟表中,此偏移量的值将为零。[...])

typeinfo指针指向用于 RTTI 的 typeinfo 对象。它总是存在的。给定类的每个虚拟表中的所有条目都必须指向相同的 typeinfo 对象。typeinfo 相等性的正确实现是检查指针相等性,但指向不完整类型的指针(直接或间接)除外。typeinfo 指针对于多态类(即具有虚函数的类)是有效的指针,而对于非多态类则为零。


更详细的顶部偏移 (根据要求)

假设您有一个D派生自基类的派生类,B1. 当您尝试将D实例强制转换为 type时会发生什么B1?由于接受B1对象的函数不知道任何关于的内容D,因此 vtable 的一部分D也必须是有效的B1vtable。这很容易——只需让Dvtable 的开头看起来像B1vtable,然后添加我们需要的任何其他条目。期待 a 的函数B1会很高兴,因为他们不会使用 vtable 的任何部分超出他们对 a 的期待B1

但是,如果Dnow派生自会发生什么B2?指向Dvtable 的指针不能既是有效的B1vtable是有效的B2vtable!B2编译器通过在我们组合的 vtable 的末尾附加一个单独的 vtable 来解决这个问题D/B1,并在我们尝试从 aD转换为 a时手动调整 vtable-pointer B2

然而,这导致了一个新问题——当我们尝试从 a回退到B2a时会发生D什么?编译器不能仅仅将 vtable-pointer 向后调整与之前调整指针相同的量,因为它实际上并不确定我们给它的对象B2是 type D!特别是,必须能够判断我们的对象是否属于 type 。为此,它需要访问对象的 RTTI,并且为此dynamic_cast<D>() D,它需要知道原始对象的 vtable 的开始在哪里。这就是 offset-to-top 值的目的——它为我们提供了到原始对象的 vtable 开始的偏移量,我们得到了对象的 RTTI,C++ 的复仇之神允许我们的作物再生长一个季节。

这个页面有一些很好的 vtable 布局示例(在表 1c下)。请注意,由于使用了虚拟继承,它们稍微复杂一些,它为每个子类的 vtable 添加了额外的偏移量。

于 2011-04-19T07:24:57.520 回答
4

也许第一个条目用于虚拟析构函数,第二个用于 RTTI 支持?但这只是一个猜测。

于 2011-04-19T07:16:52.640 回答
1

我相信引用 Itanium ABI 的答案太麻烦而无法理解。

我认为 Ruhr-Universität Bochum 和其他人的这篇论文(https://www.syssec.ruhr-uni-bochum.de/media/emma/veroeffentlichungen/2019/10/02/ACSAC19-VPS.pdf)描述了 RTTI 和 Offset - 以更友好的方式登顶。

摘自论文本身:

在此处输入图像描述

RTTI 持有一个指针,指向关于类的类型信息。除其他外,此类型信息包含类的名称及其基类。但是,RTTI 是可选的,并且经常被编译器忽略。仅在程序员使用时才需要,例如dynamic_cast 或type_info。因此,可靠的静态分析不能依赖此信息。不包含 RTTI 的类将 RTTI 字段设置为零。

当一个类像 C 类一样使用多重继承(因此有一个基本 vtable 和一个或多个子 vtables)时,需要从顶部偏移。Offset-to-Top 指定子 vtable 自己的 vtblptr 和对象开头的基本 vtblptr 之间的距离。在我们的示例中,指向 C 类子 vtable 的 vtblptr 位于对象中的偏移量 0x10 处,而指向基本 vtable 的 vtblptr 位于偏移量 0x0 处。因此,存储在 sub-vtable C 中的 Offset-to-Top 字段中的两者之间的距离是-0x10。如果 vtable 是类的基本 vtable 或不使用多重继承,则 Offset-to-Top 为 0。

于 2021-04-25T13:39:40.250 回答