【问题标题】:What is the first (int (*)(...))0 vtable entry in the output of g++ -fdump-class-hierarchy?g++ -fdump-class-hierarchy 的输出中的第一个 (int (*)(...))0 vtable 条目是什么?
【发布时间】:2011-08-08 10:35:29
【问题描述】:

对于此代码:

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

【问题讨论】:

  • 我的猜测是构造函数/析构函数......你有没有尝试添加一些看看它是否有所作为?
  • @forsvarir , @Pixie :我添加了构造函数,它没有任何区别,顺便说一句,据说,只有那些附加了 virtual 关键字的函数才能在 vtable 中获得一个条目。
  • 出于好奇,您是如何输出 vtable 的?
  • :) g++ 有 -fdump-class-hierarchy 用于 vtable 显示的选项。
  • @BlueRaja:我认为 Visual Studio 从 2008 年起也支持这一点。编译器选项是/d1reportSingleClassLayout,解释为this video 的一部分(接近尾声)。

标签: c++ gcc virtual-functions vtable


【解决方案1】:

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

【讨论】:

  • 第二个条目看起来确实像 RTTI 支持,但析构函数对于这些类来说不是虚拟的。
【解决方案2】:

这些是到顶部的偏移量(需要多重继承)和 typeinfo (RTTI) 指针。

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

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

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


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

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

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

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

This page 有一些很好的 vtable 布局示例(在 Table 1c 下)。请注意,由于使用了virtual inheritance,它们会稍微复杂一些,这会为每个子类的 vtable 添加额外的偏移量。

【讨论】:

  • 误按了输入 ^,我的意思是,在那个 vtable 中哪里是指向函数 f1 () 的指针?你给的解释我还是不太清楚,请看能不能通俗的解释一下。
  • @Anisha Kaul:嗯,它在你的问题中几乎说:指向f1 的指针在该特定实现中存储为 vtable 中的第三个元素:16 B1::f1
  • 关于这两个指针的解释,typeinfo 指针是一个指向typeinfo 实例的指针(常量),其中包含有关此特定对象是什么的信息。当您在指向具有运行时类型信息的对象的指针上调用 typeid 时,您将在此处获得 typeinfo const &
  • 用外行的话来说,到顶部的偏移量并不那么微不足道......基本上在多重继承的情况下,对不同基的强制转换将导致指向不同子对象的指针,这些子对象从指向最派生对象的指针。该偏移量允许您(嗯,dynamic_cast)恢复该偏移量并获得指向对象开头的指针。
  • struct A { int x; }; struct B { int y; virtual void foo() {} }; struct C : A, B {}; int main() { C c; C *cp = &c; A *ap = &c; B *bp = &c; cout << cp << "," << ap << "," << bp << std::endl; } 这个测试显示了向上转换如何影响指针。现在直观地能够从B* 向下转换到C*,编译器需要typeinfo 对象来知道转换是可能的,并且到base 的偏移量能够将指针移回原始位置。 (事实上​​,在这种情况下,这并不是严格需要的,但在 virtual 继承的情况下,它是,但这要复杂得多。
【解决方案3】:

我认为引用 Itanium ABI 的答案太繁琐而无法理解。

我认为 Ruhr-Universität Bochum 和其他人 (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 和一个或多个子 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。

【讨论】:

    猜你喜欢
    • 2018-10-16
    • 1970-01-01
    • 2018-03-04
    • 1970-01-01
    • 2016-03-28
    • 2011-01-26
    • 2019-08-16
    • 1970-01-01
    • 2017-05-25
    相关资源
    最近更新 更多