g ++ -fdump-class-hierarchy输出中的第一个(int(*)(...))0 vtable条目是什么?

问题描述 投票:48回答:2

对于此代码:

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)*
c++ gcc virtual-functions vtable
2个回答
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转换为B2时手动调整vtable-pointer。

然而,这导致了一个新问题 - 当我们试图从B2回归到D时会发生什么?编译器不能只调整vtable指针向前调整指针的数量,因为它实际上并不确定我们给它的B2对象是D类型!特别是,dynamic_cast<D>()必须能够判断我们的对象是否是D类型。为此,它需要访问对象的RTTI,为此,它需要知道原始对象的vtable的起始位置。这是偏移到顶部值的目的 - 它给了我们原始对象的vtable开始的偏移量,我们获得了对象的RTTI,而C ++的复仇之神允许我们的作物在另一个季节生长。

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


3
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.