我正在调试一个崩溃日志。发生崩溃是因为(c ++ - )对象的vtable指针是0x1,而对象的其余部分似乎可以从崩溃日志中看出来。
程序在尝试调用虚方法时崩溃。
我的问题:在什么情况下vtable指针变为null? operator delete是否将vtable指针设置为null?
这在OS X上使用gcc 4.0.1(Apple Inc. build 5493)发生。
答案 0 :(得分:7)
可能是记忆践踏 - 错误地写了vtable
。在C ++中有几乎无限的“实现”方法。例如,缓冲区溢出。
答案 1 :(得分:7)
您遇到的任何未定义的行为都可能导致这种情况。例如:
另请参阅问题What’s the worst example of undefined behaviour actually possible?和What are all the common undefined behaviour that a C++ programmer should know about?。
最好的办法是使用边界和内存检查器,以帮助进行大量调试。
答案 2 :(得分:6)
一个非常常见的情况:尝试从构造函数调用纯虚方法...
<强>构造强>
struct Interface
{
Interface();
virtual void logInit() const = 0;
};
struct Concrete: Interface()
{
virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
现在,假设Interface()
Interface::Interface() {}
然后一切都很好:
Concrete myConcrete;
myConcrete.pure(); // outputs "Concrete"
在构造函数之后调用pure是如此痛苦,最好将代码分解为正确吗?
Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
然后我们可以在一行中完成!!
Concrete myConcrete; // CRASHES VIOLENTLY
为什么?
因为对象是自下而上构建的。我们来看看吧。
构建Concrete
类(大致)
分配足够的内存(当然),并为_vtable分配足够的内存(每个虚函数1个函数指针,通常按照它们声明的顺序,从最左边的基数开始)
调用Concrete
构造函数(您看不到的代码)
A&GT;调用Interface
构造函数,用它的指针初始化_vtable
B个调用Interface
构造函数的主体(你写的那个)
c取代;覆盖这些方法的_vtable中的指针。具体覆盖
d取代;调用Concrete
构造函数的主体(你写的那个)
那么问题是什么?好吧,看看b>
和c>
订单;)
当您从构造函数中调用virtual
方法时,它不会执行您希望的操作。它确实转到_vtable来查找指针,但_vtable
尚未完全初始化。所以,重要的是,效果:
D() { this->call(); }
实际上是:
D() { this->D::call(); }
从构造函数中调用虚方法时,如果没有构建对象的完整动态类型,则调用当前构造函数的静态类型。
在我的Interface
/ Concrete
示例中,它表示Interface
类型,并且该方法是虚拟纯的,因此_vtable不包含实际指针(例如,0x0或0x01,如果你的编译器足够友好,可以设置调试值来帮助你)。
<强>析构强>
巧合的是,让我们检查一下Destructor案例;)
struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }
struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }
Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
std::cout << "Concrete refering to " << m_data << std::endl;
}
那么破坏会发生什么?那么_vtable工作得很好,并且调用了真正的运行时类型......这里的含义是未定义的行为,因为谁知道删除m_data
之后和Interface
析构函数被调用之前发生了什么?我没有;)
<强>结论强>
永远不要在构造函数或析构函数中调用虚方法。
如果不是这样的话,你就会留下内存腐败,运气不好;)
答案 3 :(得分:4)
我的第一个猜测是某些代码是memset()
一个类对象。
答案 4 :(得分:1)
这完全取决于实现。但是,假设在删除之后某些其他操作可能将内存空间设置为null将是非常安全的。
其他可能性包括通过一些松散的指针覆盖内存 - 实际上在我的情况下,它几乎总是这样......
也就是说,删除后不应该尝试使用对象。