5

I wonder if any C++ gurus out there could shed some light on this strange situation. One of the examples that comes with the Box2D physics engine is crashing with the message "pure virtual method called", but only with a certain compiler (and only in release build).

Box2D as you may know is a pretty solid chunk of code so I am thinking this may be a problem with the compiler, especially given that it only happens with this particular compiler. I am using mingw32 on Windows7:

> gcc.exe --version
gcc version 4.4.0 (GCC)

Below is an cut-down excerpt of the relevant parts of Box2D. You can check out the full source at:

b2Shape.h
b2CircleShape.h
b2CircleShape.cpp
SensorTest.h

//base class
class b2Shape
{
public:
    virtual ~b2Shape() {}
    virtual b2Shape* Clone(b2BlockAllocator* allocator) const = 0;
};


//sub class
class b2CircleShape : public b2Shape
{
public:
    b2CircleShape();
    b2Shape* Clone(b2BlockAllocator* allocator) const;
};

inline b2CircleShape::b2CircleShape() {}

b2Shape* b2CircleShape::Clone(b2BlockAllocator* allocator) const
{
    void* mem = allocator->Allocate(sizeof(b2CircleShape));
    b2CircleShape* clone = new (mem) b2CircleShape;
    *clone = *this;
    return clone;
}

Note the placement new in the Clone function.

Now the execution that causes the problem boils down to this:

{
    b2CircleShape shape;
    shape.Clone(allocator); //ok
}
{
    b2CircleShape shape;
    shape.Clone(allocator); //"pure virtual method called"
}

After educating myself on how a virtual method could ever be called in the first place, I tried to figure out why it was happening here, since it doesn't fit the classic case of calling a virtual function in the base class constructor. After a prolonged session of stumbling around blindly I came up with the minimal case above.

My wild guess is that the compiler is smart enough to see that these two b2CircleShape instances are not in use in the same scope, so it only allocates space for one and reuses it. After the first instance is destructed, the vtable is as expected, hosed. Then for some reason when the second instance is constructed, the vtable is not constructed again...?

I came up with two things that avoid the problem, but like I say it seems more like a compiler issue so I'm not suggesting this code needs to be changed.

Dubious fix number 1 is to comment out the virtual destructor definition in the base class. All the information I have read on this subject suggests this is not the answer. (Interestingly, I found it was not enough to simply remove the 'virtual' modifier from the base class destructor. My understanding is that compiler would provide a default destructor ~b2Shape() {} if none was specified, so why is the outcome different if I actually specify what the default would be anyway? Well, this is beside the point really...)

Not-so-dubious fix number 2 I discovered was to remove the 'inline' from the sub-class constructor. Perhaps there is something about placement new, inline construction and the reused instances in the same stack frame that does not play nicely together. (UPDATE: further checking shows the placement new to be irrelevant)

Some more research tells me that the compiler is free to do whatever it likes regarding 'inline' suggestions, so perhaps the other compilers don't have this problem because they are ignoring the 'inline'?

iforce2d
  • 8,194
  • 3
  • 29
  • 40
  • Can you show us what allocator actually is ? Are you by any chance passing allocator by value to the clone function ? – Arunmu Aug 25 '12 at 15:39
  • The source can be found here. http://code.google.com/p/box2d/source/browse/trunk/Box2D/Box2D/Common/ It seems that the allocator is irrelevant though because I can use the more typical 'new' in the Clone function with the same results. – iforce2d Aug 25 '12 at 16:05
  • 2
    Damn, C code in a C++ disguise :x I suppose you do know you are leaking memory ? (Yes it's irrelevant, but I can't think of anything in the code presented that would produce such behavior). And for a relevant comment: what's the assembly generated for the method considered ? – Matthieu M. Aug 25 '12 at 16:23
  • The two sample executions look the same to me, except for the comments. `` – Pete Becker Aug 25 '12 at 16:45
  • The code looks correct (but not efficient since it calls a default constructor and performs a following assignment (operator=)). However, could it be that your example is not what really goes on? Typically, when you receive a "pure virtual method called" message, it is because the constructors have not been called completely. So, if your example objects "shape" are actually global variables (living in the global scope or are static member variables), the constructor may not have been called. – h2stein Oct 21 '12 at 12:44

2 Answers2

1

I tried your code and got error: no matching function for call to ‘operator new(long unsigned int, void*&)‘ with g++ version 4.5.2... I'm not sure but the new syntax you use must be an internal thing... (new (mem) b2CircleShape)

However, as Matthieu pointed out, that's probably not what you want to do in C++. Creating a clone assuming you can copy your objects (and you do a copy in your code) is simply:

clone = new b2CircleShape(original);
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
  • 3
    `new (mem) b2CircleShape` is not "an internal thing", it's [placement `new`](http://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new). – DCoder Oct 06 '12 at 09:29
1

This is obvious.

A) If it works once as expected then fine. B) The fact that the error happens again can only mean it is a bug in your gcc. Yes, it's true these things can have bugs. I have seen bugs in all the compilers except for the MSVC compiler.

That error there if you get the code for GCC or Clang or whatever and find where the error happens will be due to some flags that it reads. If it works once and then fails again then the flags or bits have changed in the compilers data and that means a memory overrun or other typo in the compiler.

Sorry.

  • 1
    Funny - I've only seen bugs in Microsoft's `cl` ;) – Nik Bougalis Feb 11 '13 at 23:58
  • 1
    That said, I did not use it as much as the GCC, BCC and Delphi but there are bugs everywhere and sometimes the compiler errors are that simple! –  Feb 12 '13 at 00:00