C++ “纯虚函数调用”崩溃从何而来?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/99552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Where do "pure virtual function call" crashes come from?
提问by Brian R. Bondy
I sometimes notice programs that crash on my computer with the error: "pure virtual function call".
我有时会注意到程序在我的计算机上崩溃并显示错误:“纯虚函数调用”。
How do these programs even compile when an object cannot be created of an abstract class?
当无法创建抽象类的对象时,这些程序如何编译?
回答by Adam Rosenfield
They can result if you try to make a virtual function call from a constructor or destructor. Since you can't make a virtual function call from a constructor or destructor (the derived class object hasn't been constructed or has already been destroyed), it calls the base class version, which in the case of a pure virtual function, doesn't exist.
如果您尝试从构造函数或析构函数进行虚函数调用,则会导致它们。由于您不能从构造函数或析构函数(派生类对象尚未构造或已被销毁)进行虚函数调用,因此它调用基类版本,在纯虚函数的情况下,它不会不存在。
(See live demo here)
(在此处查看现场演示)
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
}
回答by Len Holgate
As well as the standard case of calling a virtual function from the constructor or destructor of an object with pure virtual functions you can also get a pure virtual function call (on MSVC at least) if you call a virtual function after the object has been destroyed. Obviously this is a pretty bad thing to try and do but if you're working with abstract classes as interfaces and you mess up then it's something that you might see. It's possibly more likely if you're using referenced counted interfaces and you have a ref count bug or if you have an object use/object destruction race condition in a multi-threaded program... The thing about these kinds of purecall is that it's often less easy to fathom out what's going on as a check for the 'usual suspects' of virtual calls in ctor and dtor will come up clean.
除了从具有纯虚函数的对象的构造函数或析构函数调用虚函数的标准情况外,如果在对象销毁后调用虚函数,您还可以获得纯虚函数调用(至少在 MSVC 上) . 显然,尝试这样做是一件非常糟糕的事情,但是如果您将抽象类用作接口并且您搞砸了,那么您可能会看到它。如果您使用引用计数接口并且您有引用计数错误,或者如果您在多线程程序中有对象使用/对象销毁竞争条件,则更有可能......关于这些类型的纯调用的事情是它是通常不太容易弄清楚发生了什么,因为检查 ctor 和 dtor 中虚拟调用的“常见嫌疑人”会干净利落。
To help with debugging these kinds of problems you can, in various versions of MSVC, replace the runtime library's purecall handler. You do this by providing your own function with this signature:
为了帮助调试这些类型的问题,您可以在各种版本的 MSVC 中替换运行时库的 purecall 处理程序。您可以通过为您自己的函数提供此签名来做到这一点:
int __cdecl _purecall(void)
and linking it before you link the runtime library. This gives YOU control of what happens when a purecall is detected. Once you have control you can do something more useful than the standard handler. I have a handler that can provide a stack trace of where the purecall happened; see here: http://www.lenholgate.com/blog/2006/01/purecall.htmlfor more details.
并在链接运行时库之前链接它。这使您可以控制检测到 purecall 时发生的情况。一旦你有了控制权,你就可以做一些比标准处理程序更有用的事情。我有一个处理程序,可以提供 purecall 发生位置的堆栈跟踪;请参阅此处:http: //www.lenholgate.com/blog/2006/01/purecall.html了解更多详情。
(Note you can also call _set_purecall_handler() to install your handler in some versions of MSVC).
(请注意,您还可以调用 _set_purecall_handler() 在某些版本的 MSVC 中安装您的处理程序)。
回答by Braden
Usually when you call a virtual function through a dangling pointer--most likely the instance has already been destroyed.
通常当你通过一个悬空指针调用一个虚函数时——很可能实例已经被销毁了。
There can be more "creative" reasons, too: maybe you've managed to slice off the part of your object where the virtual function was implemented. But usually it's just that the instance has already been destroyed.
也可能有更多“创造性”的原因:也许您已经设法切掉了实现虚拟功能的对象部分。但通常只是实例已经被销毁。
回答by Baiyan Huang
I ran into the scenario that the pure virtual functions gets called because of destroyed objects, Len Holgate
already have a very nice answer, I would like
to add some color with an example:
我遇到了由于对象被破坏而调用纯虚函数的情况,Len Holgate
已经有一个很好的答案,我想用一个例子添加一些颜色:
- A Derived object is created, and the pointer (as Base class) is saved somewhere
- The Derived object is deleted, but somehow the pointer is still referenced
- The pointer which points to deleted Derived object gets called
- 创建派生对象,并将指针(作为基类)保存在某处
- 派生对象被删除,但不知何故仍引用指针
- 指向已删除派生对象的指针被调用
The Derived class destructor reset the vptr points to the Base class vtable, which has the pure virtual function, so when we call the virtual function, it actually calls into the pure virutal ones.
Derived 类析构函数将vptr 指向Base 类vtable,它具有纯虚函数,所以当我们调用虚函数时,它实际上调用的是纯虚函数。
This could happen because of an obvious code bug, or a complicated scenario of race condition in multi-threading environments.
这可能是由于明显的代码错误或多线程环境中复杂的竞争条件场景而发生的。
Here is an simple example (g++ compile with optimization turned off - a simple program could be easily optimized away):
这是一个简单的示例(关闭优化的 g++ 编译 - 可以轻松优化一个简单的程序):
#include <iostream>
using namespace std;
char pool[256];
struct Base
{
virtual void foo() = 0;
virtual ~Base(){};
};
struct Derived: public Base
{
virtual void foo() override { cout <<"Derived::foo()" << endl;}
};
int main()
{
auto* pd = new (pool) Derived();
Base* pb = pd;
pd->~Derived();
pb->foo();
}
And the stack trace looks like:
堆栈跟踪如下所示:
#0 0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007ffff749b02a in __GI_abort () at abort.c:89
#2 0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x0000000000400f82 in main () at purev.C:22
Highlight:
强调:
if the object is fully deleted, meaning destructor gets called, and memroy gets reclaimed, we may simply get a Segmentation fault
as the memory has returned to the operating system, and the program just can't access it. So this "pure virtual function call" scenario usually happens when the object is allocated on the memory pool, while an object is deleted, the underlying memory is actually not reclaimed by OS, it is still there accessible by the process.
如果对象被完全删除,这意味着析构函数被调用,并且 memroy 被回收,我们可能会简单地得到一个,Segmentation fault
因为内存已经返回到操作系统,而程序无法访问它。所以这种“纯虚函数调用”的场景通常发生在对象被分配到内存池上时,当一个对象被删除时,底层内存实际上并没有被操作系统回收,它仍然可以被进程访问。
回答by David Lee
I use VS2010 and whenever I try calling destructor directly from public method, I get a "pure virtual function call" error during runtime.
我使用 VS2010,每当我尝试直接从公共方法调用析构函数时,我都会在运行时收到“纯虚函数调用”错误。
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void SomeMethod1() { this->~Foo(); }; /* ERROR */
};
So I moved what's inside ~Foo() to separate private method, then it worked like a charm.
所以我把 ~Foo() 里面的东西移到了单独的私有方法中,然后它就像一个魅力。
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void _MethodThatDestructs() {};
void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
回答by Niki
If you use Borland/CodeGear/Embarcadero/Idera C++ Builder, your can just implement
如果您使用 Borland/CodeGear/Embarcadero/Idera C++ Builder,您只需实现
extern "C" void _RTLENTRY _pure_error_()
{
//_ErrorExit("Pure virtual function called");
throw Exception("Pure virtual function called");
}
While debugging place a breakpoint in the code and see the callstack in the IDE, otherwise log the call stack in your exception handler (or that function) if you have the appropriate tools for that. I personally use MadExcept for that.
在调试时在代码中放置一个断点并查看 IDE 中的调用堆栈,否则如果您有适当的工具,则将调用堆栈记录在您的异常处理程序(或该函数)中。我个人为此使用 MadExcept。
PS. The original function call is in [C++ Builder]\source\cpprtl\Source\misc\pureerr.cpp
附注。原始函数调用在[C++ Builder]\source\cpprtl\Source\misc\pureerr.cpp
回答by BCS
I'd guess there is a vtbl created for the abstract class for some internal reason (it might be needed for some sort of run time type info) and something goes wrong and a real object gets it. It's a bug. That alone should say that something that can't happen is.
我猜想出于某种内部原因(可能需要某种运行时类型信息)为抽象类创建了一个 vtbl,并且出现了问题并且一个真实的对象得到了它。这是一个错误。仅此一点就应该说不可能发生的事情是。
Pure speculation
纯猜测
edit:looks like I'm wrong in the case in question. OTOH IIRC some languages do allow vtbl calls out of the constructor destructor.
编辑:看起来我在有问题的情况下错了。OTOH IIRC 某些语言确实允许从构造函数析构函数中调用 vtbl。
回答by 1800 INFORMATION
Here is a sneaky way for it to happen. I had this essentially happen to me today.
这是它发生的一种偷偷摸摸的方式。我今天基本上发生了这种情况。
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();