8

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 question: 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).

Tobias
  • 6,388
  • 4
  • 39
  • 64
  • Can you clarify if the function pointer in the vtable is 0 or if it's the this pointer that is null? Or is it a pointer in the object that is pointing to the current vtable? – Timo Geusch Jan 15 '10 at 14:38
  • The vtable pointer is 1. The crash occurs while trying to execute `call *0x1c(%eax)` (in at&t syntax) and the value of eax is 1 (not zero as I stated incorrectly). – Tobias Jan 15 '10 at 14:43
  • Run it in gdb, set a watch-point at the address, see what's writing it. – Nikolai Fetissov Jan 15 '10 at 15:20
  • Either you have a bug somwhere else that is trampling memory. Or your object is not fully formed (You can __not__ call virtual methods during construction or destruction). There is no definitive answer to your question as the standard has nothing to say about v-tables and each compiler is completely different. – Martin York Jan 15 '10 at 18:22
  • Nikolai: "I am currently debugging a crashlog." Martin: Of course there is a bug somewhere in the code, and it is none of the trivial ones. It has nothing to do with the c++ standard. This is about finding out what bug can cause this specific behavior using gcc 4.0.1 (Apple Inc. build 5493). – Tobias Jan 19 '10 at 14:14

5 Answers5

9

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.

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
8

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.

Community
  • 1
  • 1
Daniel Daranas
  • 22,454
  • 9
  • 63
  • 116
7

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

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Thanks very much for your detailed answer. In my case, this is not the case, unfortunately. – Tobias Jan 19 '10 at 13:43
4

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

John Dibling
  • 99,718
  • 31
  • 186
  • 324
1

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.

Kornel Kisielewicz
  • 55,802
  • 15
  • 111
  • 149