为什么我的vfptr表条目混淆了?

时间:2014-09-30 04:02:29

标签: c++ virtual vtable

我对以下问题感到困惑(MSVC ++ 2012):

我有一组纯虚拟类,用于定义接口和一些具有多重继承的派生类,以便实现。我使用相同的基类集来实现两个不同的结束类(在后面的类层次结构中," Foo"类为这两个结束类中的每一个实现不同的虚函数)。这两个结束类中的一个编译没有问题,但另一个结合了其vfptr表条目。我对可能造成这种情况的原因感到困惑,并且无法指出错误。所以我在这里向C ++专家提交。

这是以简化方式的类层次结构:

class IBaz1 {
   virtual void IBaz1_SC() = 0;
   virtual void IBaz1_IVR() = 0;
   virtual void IBaz1_ICIVR() = 0;
};

class IBaz2 {
   virtual void IBaz2_JTHL() = 0;
   virtual void IBaz2_UP() = 0;
   virtual void IBaz2_RKF() = 0;
   virtual void IBaz2_GVC() = 0;
   virtual void IBaz2_GAI() = 0;
};

class IBaz3 {
   virtual void IBaz3_SCJ() = 0;
   virtual void IBaz3_CJS() = 0;
};

class IQux {
   virtual void IQux_RIU() = 0;
   virtual void IQux_CTAU() = 0;
   virtual void IQux_D() = 0;
   virtual void IQux_AD() = 0;
   virtual void IQux_IDIP() = 0;
   virtual void IQux_GLT() = 0;
   virtual void IQux_SLT() = 0;
   virtual void IQux_GF() = 0;
};

class Qux : public IQux {
   virtual void Qux::IQux_D();
   virtual void Qux::IQux_AD();
   virtual void Qux::IQux_IDIP();
};

class Bar : public IBaz1, public IBaz2, public IBaz3, public Qux {
   virtual void IBaz1_ICIVR();
   virtual void IBaz2_UP();
   virtual void IQux_RIU();
   virtual void IQux_GLT();
   virtual void IQux_SLT();
   virtual void IQux_GF();
};

class FooA (and FooB) : public Bar {
   virtual void IBaz1_SC();
   virtual void IBaz1_IVR();
   virtual void IBaz2_JTHL();
   virtual void IBaz2_RKF();
   virtual void IBaz2_GVC();
   virtual void IBaz2_GAI();
   virtual void IBaz3_SCJ();
   virtual void IBaz3_CJS();
   virtual void IQux_CTAU();
};

      Expected vfptr             result mixed-up vfptr
      ======================     ====================================
FooA (is also a COM)       FooB (is a straight inheritance)
  Bar                        Bar
    IBaz1                      IBaz1
      [0] Foo::IBaz1_SC()        [0] Foo::IBaz1_SC()
      [1] Foo::IBaz1_IVR()       [1] Foo::IBaz1_IVR()
      [2] Bar::IBaz1_ICIVR()     [2] Bar::IBaz1_ICIVR()
    IBaz2                      IBaz2
      [0] Foo::IBaz2_JTHL()      [0] Bar::IQux_GLT()
      [1] Bar::IBaz2_UP()        [1] Bar::IQux_SLT()
      [2] Foo::IBaz2_RKF()       [2] Foo::IBaz2_JTHL()
      [3] Foo::IBaz2_GVC()       [3] Bar::IBaz2_UP()
      [4] Foo::IBaz2_GAI()       [4] Foo::IBaz2_RKF()
    IBaz3                      IBaz3
      [0] Foo::IBaz3_SCJ()       [0] Foo::IBaz3_SCJ()
      [1] Foo::IBaz3_CJS()       [1] Foo::IBaz3_CJS()
    Qux                        Qux
      IQux                       IQux
        [0] Bar::IQux_RIU()        [0] Bar::IQux_RIU()
        [1] Foo::IQux_CTAU()       [1] Foo::IQux_CTAU()
        [2] Qux::IQux_D()          [2] Qux::IQux_D()
        [3] Qux::IQux_AD()         [3] Qux::IQux_AD()
        [4] Qux::IQux_IDIP()       [4] Qux::IQux_IDIP()
        [5] Bar::IQux_GLT()        [5] [thunk]:Bar::IQux_GLT`adjustor{8}'()
        [6] Bar::IQux_SLT()        [6] [thunk]:Bar::IQux_SLT`adjustor{8}'()
        [7] Bar::IQux_GF()         [7] [thunk]:Bar::IQux_GF`adjustor{8}'()

两个最终类是" FooA"和" FooB"。在实际代码中,它们以相同的方式定义,除了FooA也是AxtiveX COM对象,因此除了继承自Bar,因此从IBaz1,IBaz2,IBaz3和Qux继承之外,它还继承了许多ATL模板。

我已将名称缩减为大写字母,因此它们适合页面宽度。例如,IBaz1_SC的真实名称是" SetCapture"在实际代码中,IBaz1_IVR的真名是" InvalidateViewportRect"。

我并排显示两个vfptr表,因此可以轻松比较它们。我展示的vfptr表格布局是我从MSVC调试器获得的布局" Auto"我扩展了所有继承类的vtable后的窗口。左边的vfptr表来自构建和运行良好的结束类,即" FooA",它也是一个COM继承的类。虽然正确的vfptr表来自未运行的结束类,但它是" FooB",它是不是 COM继承的类,但只是一个直接继承的类。

注意IBaz2 vfptr中的条目。 IBaz2 [0]中的指针已被下推到IBaz2 [2],IBaz2 [1]到IBaz2 [3]等。而IBaz2 [0]和[1]中的条目指向IQuz [5]的函数[6]。而现在IQux [5]到[7]中的指针正在通过一个thunk并有一个调整器。

程序在某些时候崩溃,它调用IBaz2_JTHL而不是调用IBaz2_RKF。

可能导致这种情况的原因是什么?我应该寻找什么样的编码错误?任何可能有助于理解问题并解决此问题的解释都将非常感激。

如果需要,我可以提供更好的代码提取。

2 个答案:

答案 0 :(得分:1)

从提出的问题来看,很明显,由此产生的vtable布局并不符合您的期望。这很可能是由于你的期望而不是其他任何事情。

调整器thunk通常是由非零偏移的基类引起的,而这些基类又是由多重继承引起的。

已知MSVC折叠相同的功能,这可能会导致混淆。例如,如果其中一些函数是无操作,则它们的vtable条目可能都指向相同的无操作实现。

你的代码没有运行可能是因为你绕过C ++并直接访问vtable(我假设这是因为你展示了一个vtable布局,它甚至没有在C ++标准中定义)

<强> [编辑]

新信息几乎证实了这一点:非标准访问= COM。 COM没有C ++强大,也无法处理多重继承。 COM误解了使用MI的C ++类中的vtable,导致上面出现的问题。

答案 1 :(得分:0)

所以我找到了这个vtable混音的原因。 IBaz2类定义了两次。第二个定义是在一个旧的源文件中,该文件刚刚从项目中删除,但仍然在磁盘上并在预编译的头文件中引用。

这个第二个和旧的定义具有在错误的vtable中显示的所有纯虚函数。我假设,因为它在预编译头中,旧的定义在编译时用于vtable布局,但访问它的代码是从新定义生成的。

为了弄清楚这一点,我开始重命名所涉及的类。在修复与重命名相关的问题时,我偶然发现了对旧文件的引用。

现在一切都按预期工作了。我本以为编译器会抱怨类的第二个定义。