在什么情况下vtable指针可以为null(或0x1)?

时间:2010-01-15 14:35:21

标签: c++ macos gcc crash vtable

我正在调试一个崩溃日志。发生崩溃是因为(c ++ - )对象的vtable指针是0x1,而对象的其余部分似乎可以从崩溃日志中看出来。

程序在尝试调用虚方法时崩溃。

我的问题:在什么情况下vtable指针变为null? operator delete是否将vtable指针设置为null?

这在OS X上使用gcc 4.0.1(Apple Inc. build 5493)发生。

5 个答案:

答案 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类(大致)

的说明
  1. 分配足够的内存(当然),并为_vtable分配足够的内存(每个虚函数1个函数指针,通常按照它们声明的顺序,从最左边的基数开始)

  2. 调用Concrete构造函数(您看不到的代码)

    A&GT;调用Interface构造函数,用它的指针初始化_vtable

    B个调用Interface构造函数的主体(你写的那个)

    c取代;覆盖这些方法的_vtable中的指针。具体覆盖

    d取代;调用Concrete构造函数的主体(你写的那个)

  3. 那么问题是什么?好吧,看看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将是非常安全的。

其他可能性包括通过一些松散的指针覆盖内存 - 实际上在我的情况下,它几乎总是这样......

也就是说,删除后不应该尝试使用对象。