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

Tob*_*ias 7 c++ crash macos gcc vtable

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

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

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

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

Eli*_*sky 8

可能是一个记忆践踏 - 一些vtable错误的写作.在C++中有几乎无限的"实现"方法.例如,缓冲区溢出.


Dan*_*nas 8

您遇到的任何未定义的行为都可能导致这种情况.例如:

  • 指针算术或其他使程序写入无效内存的错误.
  • 未初始化的变量,无效的演员......
  • 以多态方式处理数组可能会将此作为次要效果.
  • 删除后尝试使用对象.

另请参阅问题实际可能的未定义行为的最坏例子是什么?什么是所有常见的未定义行为,一个C++程序员应该知道的?.

最好的办法是使用边界和内存检查器,以帮助进行大量调试.


Mat*_* M. 6

一个非常常见的情况:尝试从构造函数中调用纯虚方法...

构造函数

struct Interface
{
  Interface();
  virtual void logInit() const = 0;
};

struct Concrete: Interface()
{
  virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};
Run Code Online (Sandbox Code Playgroud)

现在,假设以下实现 Interface()

Interface::Interface() {}
Run Code Online (Sandbox Code Playgroud)

一切都很好:

Concrete myConcrete;
myConcrete.pure();    // outputs "Concrete"
Run Code Online (Sandbox Code Playgroud)

在构造函数之后调用pure是如此痛苦,最好将代码分解为正确吗?

Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)
Run Code Online (Sandbox Code Playgroud)

然后我们可以在一行中完成!

Concrete myConcrete;  // CRASHES VIOLENTLY
Run Code Online (Sandbox Code Playgroud)

为什么?

因为对象是自下而上构建的.我们来看看吧.

构建Concrete类的说明(粗略)

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

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

    a>调用Interface构造函数,用它的指针初始化_vtable

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

    c>覆盖_vtable中用于这些方法的指针.具体覆盖

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

所以有什么问题 ?好吧,看看b>c>订购;)

当您virtual从构造函数中调用方法时,它不会执行您希望的操作.它确实转到_vtable来查找指针,但_vtable尚未完全初始化.因此,对于所有重要的事情,影响:

D() { this->call(); }
Run Code Online (Sandbox Code Playgroud)

实际上是:

D() { this->D::call(); }
Run Code Online (Sandbox Code Playgroud)

从构造函数中调用虚方法时,如果没有构建对象的完整动态类型,则调用当前构造函数的静态类型.

在我的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;
}
Run Code Online (Sandbox Code Playgroud)

那么毁灭会发生什么?那么_vtable工作得很好,并且调用了真正的运行时类型......这里的含义是未定义的行为,因为谁知道m_data在删除它之后和Interface调用析构函数之前发生了什么?我不 ;)

结论

永远不要在构造函数或析构函数中调用虚方法.

如果不是这样,你就会留下内存腐败,运气不好;)