开发者

Under what circumstances can a vtable pointer be null (or 0x1)?

I am currently debugging a crashlog. The crash occurs because the vtable pointer of a (c++-) object is 0x1, while the rest of the object seems to be ok as far as I can tell from the crashlog.

The program crashes when it tries to call a virtual method.

My questio开发者_高级运维n: Under what circumstances can a vtable pointer become null? Does operator delete set the vtable pointer to null?

This occurs on OS X using gcc 4.0.1 (Apple Inc. build 5493).


Could be a memory trample - something writing over that vtable by mistake. There is a nearly infinite amount of ways to "achieve" this in C++. A buffer overflow, for example.


Any kind of undefined behaviour you have may lead to this situation. For example:

  • Errors in pointer arithmetic or other that make your program write into invalid memory.
  • Uninitialized variables, invalid casts...
  • Treating an array polymorphically might cause this as a secondary effect.
  • Trying to use an object after delete.

See also the questions What’s the worst example of undefined behaviour actually possible? and What are all the common undefined behaviour that a C++ programmer should know about?.

Your best bet is to use a bounds and memory checker, as an aid to heavy debugging.


A very common case: trying to call a pure virtual method from the constructor...

Constructors

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

struct Concrete: Interface()
{
  virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};

Now, suppose the following implementation of Interface()

Interface::Interface() {}

Then everything is fine:

Concrete myConcrete;
myConcrete.pure();    // outputs "Concrete"

It's such a pain to call pure after the constructor, it would be better to factorize the code right ?

Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)

Then we can do it in one line!!

Concrete myConcrete;  // CRASHES VIOLENTLY

Why ?

Because the object is built bottom up. Let's look at it.

Instructions to build a Concrete class (roughly)

  1. Allocate enough memory (of course), and enough memory for the _vtable too (1 function pointer per virtual function, usually in the order they are declared, starting from the leftmost base)

  2. Call Concrete constructor (the code you don't see)

    a> Call Interface constructor, which initialize the _vtable with its pointers

    b> Call Interface constructor's body (you wrote that)

    c> Override the pointers in the _vtable for those methods Concrete override

    d> Call Concrete constructor's body (you wrote that)

So what's the problem ? Well, look at b> and c> order ;)

When you call a virtual method from within a constructor, it doesn't do what you're hoping for. It does go to the _vtable to lookup the pointer, but the _vtable is not fully initialized yet. So, for all that matters, the effect of:

D() { this->call(); }

is in fact:

D() { this->D::call(); }

When calling a virtual method from within a Constructor, you don't the full dynamic type of the object being built, you have the static type of the current Constructor invoked.

In my Interface / Concrete example, it means Interface type, and the method is virtual pure, so the _vtable does not hold a real pointer (0x0 or 0x01 for example, if your compiler is friendly enough to setup debug values to help you there).

Destructors

Coincidently, let's examine the Destructor case ;)

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;
}

So what happens at destruction ? Well the _vtable works nicely, and the real runtime type is invoked... what it means here however is undefined behavior, because who knows what happened to m_data after it's been deleted and before Interface destructor was invoked ? I don't ;)

Conclusion

Never ever call virtual methods from within constructors or destructors.

If it's not that, you're left with a memory corruption, tough luck ;)


My first guess would be that some code is memset()'ing a class object.


This is totaly implementation dependant. However it would be quite safe to assume that after delete some other operation may set the memory space to null.

Other possibilities include overwrite of the memory by some loose pointer -- actually in my case it's almost always this...

That said, you should never try to use an object after delete.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜