Search code examples
box2d

Box2d engine as std::vector


A box2d engine is created as usual with “Physics a”. This works fine in the example. But, if we are adding a new instance as std::vector with myphysics.push_back({}); the following error is shown on the command-line after executing the binary file:

“double free or corruption (!prev) Aborted (core dumped)”

I have absolutely no idea, what the message mean or what the problem is with a vector of box2d engines. Here is the sourcecode:

// g++ -std=c++14 -lBox2D rrt.cpp
#include <Box2D/Box2D.h>
#include <vector>

class Physics {
public:
  b2World world{b2Vec2(0.f, 9.8f)};
};

class RRT {
public:
  std::vector<Physics> myphysics;
  RRT() {
    myphysics.push_back({}); 
    //Physics a;
  }
};

int main()
{ 
  RRT myrrt;
}

Solution

  • The problem essentially is that b2World is not designed to be copyable.

    I can reproduce a double free simply using the following code block:

    {
        b2World woo{b2Vec2(0.f, 9.8f)};
        b2World poo{woo};
    }
    

    This problem can be recognized via source code analysis.

    Taking a look at the b2World class, we can see that it has no user defined copy constructor and no user defined copy assignment method. Furthermore the code uses no mechanism to prevent the compiler from defining these. So the compiler follows the rules for special member functions and automatically generates the copy constructor and copy assignment operator. Neither of these automatically generated methods will know how to deal with any dynamically allocated memory that b2World causes to be allocated (like through its composition of a b2BroadPhase instance). These automatically generated methods simply call component instances' copy methods which in this case results in any pointers to allocated memory to have those addresses copied. Then on destruction, a destructor like that for the b2BroadPhase class calls the C-library free function for that allocated memory for the original instance and again for every copy.

    Bam!!... double free!

    If you are wondering, here's some ways to avoid this problem:

    1. Don't do anything that invokes the compiler defined b2World copy constructor or copy assignment operator. For example, instead of using a vector of b2World instances, make it a vector of pointers to b2World instances. You might do that by having your Physics class's world member defined instead as something like: std::unique_ptr<b2World> world{std::make_unique<b2World>(b2Vec2(0.f, 9.8f))};. If you don't mind rebuilding Box2D, you might also want to add explicitly deleted definitions of these special functions to help with avoiding these copy operations (just add the lines: b2World(const b2World& o) = delete; b2World& operator=(const b2World&) = delete; into the class definition for b2World in its header file).
    2. Update the Box2D code to properly handle copy construction and copy assignment. This is much harder to do correctly in my opinion than option 1 however.
    3. Use an alternate physics engine that's designed to support copy construction and copy assignment. Shameless plug: I'm partial to PlayRho as such an alternative. It's basically a derived work (fork) of Box2D. PlayRho isn't without any problems either, but besides supporting copying, it has other features and has over 99% unit test coverage.