C ++中的多个Vtables广告VPointers

时间:2015-06-17 02:07:05

标签: c++ vtable

我一直在阅读vtable和指针,但我还有一些问题。例如:

#include <iostream>

using namespace std;

class A
{
 public:
  virtual void PrintA()=0;  //1 vtable and 1 vpointer
};

class B
{
 public:
  virtual void PrintB()=0; //1 vtable and 1 vpointer
};

class Parent: public A, public B
{
 public:
  void PrintA();
  void PrintB();                // 3 vtables and 3 vpointers, right?
  virtual void PrintChild()=0;
};


void Parent::PrintA()
{
 cout<<"A";
}

void Parent::PrintB()
{
 cout<<"B";
}


class Child: public Parent
{
 public:
   void PrintChild(); //3 vtables and 3 vpointers
};

void Child::PrintChild()
{
 cout<<"Child";
}

int main()
{

  Parent* p1= new Child();
  p1->PrintChild();
  delete p1;
  return 0;
}

问题1:父母和孩子将有3个vtable和3个vpointers吗?

问题2:p1将如何知道使用哪个vpointer?我听说这取决于编译器,但我只是希望有人澄清。

1 个答案:

答案 0 :(得分:1)

是的,确定的答案取决于编译器。甚至不能保证用vtable实现虚拟调度。

编译器通常会遵循特定平台的ABI。在许多系统中,GCC实现了为IA-64发明的特定ABI,但随后被移植到其他系统。这在网上很容易获得,有GCC网站的链接。

在Linux上至少可以看到更多关于vtables的一种方法是使用gdb,使用-g编译一个小示例程序,并使用info vtbl来探索vtable 。然而,由于涉及虚拟析构函数的调试信息的GCC错误,目前这有点棘手;所以只要确保总有除析构函数之外的其他方法。

我编译了您的程序,并在gdb初始化后停在p1。然后:

(gdb) info vtbl p1
vtable for 'Parent' @ 0x400a10 (subobject @ 0x602010):
[0]: 0x400806 <Parent::PrintA()>
[1]: 0x400810 <Parent::PrintB()>
[2]: 0x400824 <Child::PrintChild()>

vtable for 'B' @ 0x400a38 (subobject @ 0x602018):
[0]: 0x40081a <non-virtual thunk to Parent::PrintB()>

在这里,您可以看到ParentChild实际上只有两个 vtable,而不是三个。这是因为在这个ABI中,通过扩展父类'vtable来实现单继承;在这种情况下,A的扩展名也会以同样的方式处理。

关于p1如何知道使用哪个vtable:它取决于用于进行调用的实际类型。

在代码中,p1->PrintChild()被调用,而p1Parent*。在这里,调用将通过您在上面看到的第一个vtable进行 - 因为没有其他任何意义,因为PrintChild未在B中声明。在此ABI中,vtable存储在对象的第一个插槽中:

(gdb) p *(void **)p1
$1 = (void *) 0x400a10 <vtable for Child+16>

现在,如果您将代码更改为将p1转换为B*,那么会发生两件事。首先,指针的原始位将改变,因为新指针将指向完整对象的子对象。其次,该子对象的vtable槽指向上面提到的第二个vtable。在这种情况下,有时会向子对象应用特殊偏移以再次查找完整对象。当使用virtual继承时,还会有一些特殊的调整(这会使对象布局复杂化,因为所讨论的超类只在布局中出现一次)。

你可以看到这样的变化:

(gdb) p (B*)p1
$2 = (B *) 0x602018
(gdb) p *(void**)(B*)p1
$3 = (void *) 0x400a38 <vtable for Child+56>

这几乎都是Linux上常用的ABI所特有的。其他系统可能会做出不同的选择。