"纯虚函数调用"崩溃从何而来?

时间:2020-03-06 14:24:40  来源:igfitidea点击:

我有时会注意到程序在计算机上崩溃,并显示以下错误:"纯虚拟函数调用"。

无法创建抽象类的对象时,这些程序甚至如何编译?

解决方案

我猜是由于某种内部原因(为某种运行时类型信息可能需要)为抽象类创建了一个vtbl,并且出了点问题,一个真实的对象得到了它。这是一个错误。仅此而已就意味着不可能发生的事情。

纯粹的猜测

编辑:看起来我在有关情况下是错的。 OTOH IIRC某些语言确实允许从构造函数析构函数中进行vtbl调用。

如果我们尝试从构造函数或者析构函数进行虚拟函数调用,则可能会导致它们。由于我们不能从构造函数或者析构函数进行虚拟函数调用(派生类对象尚未构建或者已被销毁),因此它将调用基类版本,在纯虚拟函数的情况下,该基类版本不会不存在。

(在此处观看现场演示)

class Base
{
public:
    Base() { doIt(); }  // DON'T DO THIS
    virtual void doIt() = 0;
};

void Base::doIt()
{
    std::cout<<"Is it fine to call pure virtual function from constructor?";
}

class Derived : public Base
{
    void doIt() {}
};

int main(void)
{
    Derived d;  // This will cause "pure virtual function call" error
}

通常,当我们通过悬空指针调用虚拟函数时,很可能实例已被破坏。

也可能有更多的"创造性"原因:也许我们已经设法将对象的实现虚拟功能的部分切开了。但是通常只是实例已被销毁。

这是一种偷偷摸摸的实现方式。今天,我基本上发生了这种情况。

class A
{
  A *pThis;
  public:
  A()
   : pThis(this)
  {
  }

  void callFoo()
  {
    pThis->foo(); // call through the pThis ptr which was initialized in the constructor
  }

  virtual void foo() = 0;
};

class B : public A
{
public:
  virtual void foo()
  {
  }
};

B b();
b.callFoo();

除了从具有纯虚函数的对象的构造函数或者析构函数中调用虚函数的标准情况之外,如果在对象被销毁后调用虚函数,则还可以获得纯虚函数调用(至少在MSVC上) 。显然,这是一件很不好的尝试,但是如果我们使用抽象类作为接口,却搞砸了,那么我们可能会发现。如果我们使用的是引用计数接口,并且有引用计数错误,或者在多线程程序中有对象使用/对象破坏竞争的情况,则这种可能性更大。关于这些purecall的事情是通常不容易弄清发生了什么,因为检查ctor和dtor中虚拟调用的"通常可疑对象"会很清楚。

为了帮助调试这类问题,我们可以在各种版本的MSVC中,替换运行时库的purecall处理程序。我们可以通过为自己的函数提供以下签名来做到这一点:

int __cdecl _purecall(void)

并链接它,然后再链接运行时库。这使我们可以控制检测到纯调用时发生的情况。一旦有了控制权,我们就可以做比标准处理程序更有用的事情。我有一个处理程序,可以提供纯调用发生位置的堆栈跟踪;有关更多详细信息,请参见此处:http://www.lenholgate.com/blog/2006/01/purecall.html。

(请注意,我们也可以调用_set_purecall_handler()在某些版本的MSVC中安装处理程序)。