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

时间:2023-04-12
本文介绍了g++ -fdump-class-hierarchy 的输出中的第一个 (int (*)(...))0 vtable 条目是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

对于此代码:

class B1{上市:虚拟无效 f1() {}};D 类:公共 B1 {上市:无效 f1() {}};int主(){B1 *b1 = 新 B1();D *d = 新 D();返回0;}

编译后,我用g++ -fdump-class-hierarchy得到的vtable是:

B1 的 VtableB1::_ZTV2B1:3u 条目0 (int (*)(...))08 (int (*)(...))(& _ZTI2B1)16 B1::f1D 的 VtableD::_ZTV1D:3u 条目0 (int (*)(...))08 (int (*)(...))(& _ZTI1D)16 D::f1

我无法理解像 (int ()(...))0* 这样的条目对应什么.当然,它的意思是,它是一个返回 int 并接受无限数量参数的函数,我不明白任何进一步的内容.这个函数指针对应哪个函数?你怎么知道的?我的是 64 位机器.

第二个函数指针在末尾有一个地址??对应谁?

编辑

编译器,我用的是g++:

g++ -v使用内置规范.目标:x86_64-suse-linux配置为:../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线程模型:posix*gcc 版本 4.4.1 [gcc-4_4-branch 修订版 150839] (SUSE Linux)*

解决方案

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

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

<块引用>

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

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

<小时>

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

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

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

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

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

For this code:

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

After compilation, the vtable I get with g++ -fdump-class-hierarchy is:

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

I failed to understand what do the entries like (int ()(...))0* correspond to. Of course it means something like, it is a function which returns an int and takes unlimited number of arguments, I don't understand anything further. To which function does this function pointer correspond to? and how do you know that? Mine is a 64 bit machine.

The second function pointer has an address associated at end?? To whom does that correspond to?

EDIT

The compiler, I use is 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)*

解决方案

Those are the offset-to-top (needed for multiple inheritence) and typeinfo (RTTI) pointers.

From the Itanium ABI (you are not using the Itanium compiler, but their description of this is really good):

The offset to top holds the displacement to the top of the object from the location within the object of the virtual table pointer that addresses this virtual table, as a ptrdiff_t. It is always present. The offset provides a way to find the top of the object from any base subobject with a virtual table pointer. This is necessary for dynamic_cast in particular.
(In a complete object virtual table, and therefore in all of its primary base virtual tables, the value of this offset will be zero. [...])

The typeinfo pointer points to the typeinfo object used for RTTI. It is always present. All entries in each of the virtual tables for a given class must point to the same typeinfo object. A correct implementation of typeinfo equality is to check pointer equality, except for pointers (directly or indirectly) to incomplete types. The typeinfo pointer is a valid pointer for polymorphic classes, i.e. those with virtual functions, and is zero for non-polymorphic classes.


Offset-to-top in more detail (by request)

Let's say you have a derived class D that derives from a base class, B1. What happens when you try to cast a D instance to type B1? Since functions that take a B1 object don't know anything about D, part of the D vtable must also be a valid B1 vtable. This is easy enough - just make the start of the D vtable look like a B1 vtable, and add on any additional entries we need after that. Functions expecting a B1 will be happy, because they won't use any part of the vtable beyond what they're expecting for a B1.

However, what happens if D now also derives from B2? The pointer to the D vtable can't be both a valid B1 vtable and a valid B2 vtable! The compiler solves this by appending a separate B2 vtable to the end of our combined D/B1 vtable, and adjusts the vtable-pointer manually when we try to cast from a D to a B2.

However, this leads to a new problem - what happens when we try to cast back from a B2 to a D? The compiler can't just adjust the vtable-pointer backwards by the same amount it adjusted the pointer previously, because it doesn't actually know for sure that the B2 object we're giving it is of type D! In particular, dynamic_cast<D>() must be able to tell if our object is or isn't of type D. For that, it needs to access the object's RTTI, and for that, it needs to know where the start of the original object's vtable is. This is the purpose of the offset-to-top value - it gives us the offset to the start of the original object's vtable, we get our object's RTTI, and the vengeful god of C++ allows our crops to grow for another season.

This page has some good examples of vtable layouts (under Table 1c). Note that they are slightly more complicated due to the use of virtual inheritance, which adds an extra offset to the vtable of each child class.

这篇关于g++ -fdump-class-hierarchy 的输出中的第一个 (int (*)(...))0 vtable 条目是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:为什么要加入守卫? 下一篇:何时以及如何使用 GCC 的堆栈保护功能?

相关文章