【问题标题】:Virtual tables memory location虚拟表内存位置
【发布时间】:2013-05-08 09:12:40
【问题描述】:

给定以下代码:

namespace Example1 {

class Base1 {
public:
    Base1() {}
    virtual ~Base1() {}
protected:
    float data_Base1;
};

class Base2 {
public:
    Base2() {}
    virtual ~Base2() {}
protected:
    float data_Base2;
};

class Derived : public Base1, public Base2 {
public:
    Derived() {}
    virtual ~Derived() {}
protected:
    float data_Derived;
};

class Derived2 : public Base1 {
public:
    Derived2() {}
    virtual ~Derived2() {}
protected:
    float data_Derived2;
};

}

int main (void)
{
using namespace Example1;

Base2* pbase2 = new Derived;        
Base1* b = new Base1();
      Base1* b2 = new Base1();
Derived* d = new Derived;
Derived* d2= new Derived;

Derived2* dd = new Derived2;
}

使用 Visual Studio 2012 的编译器,似乎在多重继承下,派生类包含 n-1 个额外的虚拟表。这正是派生类发生的情况。

但它似乎也发生在 Derived2 (仅继承自 Base1 类)

这里是 dd 内存映射:

Example1::Base1
  __vfptr
     [0]     0x00c4127b

这是 b 内存映射:

__vfptr
    [0]      0x00c411ae

如您所见,第一个虚拟表槽的地址不同.. 例如 b 和 b2 具有相同的虚拟表。

好的,现在回答两个问题:

1) 为什么它们不共享同一个 Base1 虚拟表? (Derived2 和 Base1 对象)

2) 为什么派生类甚至需要保存 n-1 个虚拟表? (当N表示Derived类继承的类数时)

谢谢!

【问题讨论】:

  • 顺便说一句。这些是实现细节,可能会因实现而异,最好问的可能是制造商,我们在这里大多可以猜测

标签: c++


【解决方案1】:

首先,Derived2 是与Base1 不同的类型,因此它需要一些除虚函数表之外的其他信息。其次,至少Derived2 的析构函数与Base1 中的析构函数不同,因此即使表中只有虚函数,该条目 也必须不同。 我不确定 MSVC 如何在多态类型上实现 RTTI,但必须有一些与虚函数不同的类型的标识,例如启用dynamic_casts。所以第一个条目很可能是指向 RTTI 的指针。我目前没有 MSVC,但你可以试试这个:

struct Base {
  virtual void foo() {};
  virtual void bar() {};
  virtual ~Base();
};

struct Derived {
  virtual void foo() {};
  virtual ~Derived();
};

int main() {
  Base* b1 = new Base;
  Base* b2 = new Derived;
};

现在检查两个创建对象的__vfptr 的前四个或五个元素,我猜你会看到一个相同的条目——它是指向Base::bar 的指针。其他的(指向 RTTI、foo 和析构函数的指针)应该不同。
这里有一些猜测:也许你可以在内存中看到指针指向的不同区域,因为 RTTI 指针可能指向数据段,而虚函数指针指向代码段。

更新: vtable 本身不需要 RTTI 条目 - 某些编译器可能仅通过 比较 vtable 的地址来实现 RTTI。

【讨论】:

    【解决方案2】:

    每个类都有自己的 vtable。在这种情况下,每个类都有一个唯一的虚拟析构函数,这本身就意味着 vtable 需要不同。如果您要构造一个没有任何不同虚函数的类,编译器可能会决定“重用”同一个 vtable。但不能保证。

    如果一个类派生自多个类,那么每个类都需要一个 vtable,它具有任何类型的虚函数。这样一来,如果将类转换(通过使用指向基类的指针或 dynamic_cast)为基类之一,则可以调用两个基类的虚函数。

    另请注意:编译器如何处理 vtables 完全取决于编译器,并且不保证它们如何工作的任何方面。

    【讨论】:

    • 如果编译器使用 RTTI 的 vtable(大多数/所有编译器都这样做),即使它们的所有虚函数都相同,它们也必须对不同的类有所不同,即没有虚函数dtor 并且没有在派生类中重写或添加虚函数。
    猜你喜欢
    • 2015-02-09
    • 2021-10-07
    • 1970-01-01
    • 2018-07-09
    • 2011-12-18
    • 2015-11-23
    • 2015-04-17
    • 2013-06-03
    • 1970-01-01
    相关资源
    最近更新 更多