Question

I'm writing a bit of code for searching a maze with BFS in C++ (my primary language is Python, but I wanted to excersise my C++ brain a bit...), and I stumbled across this strange error.

Here are the relevant data structures:

struct Maze {
  std::pair<int, int> start;
  std::pair<int, int> goal;
  std::pair<int,int> dims;
  std::set<std::pair<int, int> > passable;
};

struct SearchNode {
  std::pair<int, int> cell;
  Maze* pMaze;
  SearchNode* parent;
  std::vector<SearchNode*> children;
};

Assume that I've already got a method void parseFile(Maze* maze, char* filename) that reads in a maze text file, storing the (row, col) pairs of the start and goal squares as well as a set corresponding to the (row, col) pairs that are "passable" in the maze.

There are a few other functions as well:

bool isPassable(Maze* maze, std::pair<int,int> testCell);
std::vector<SearchNode*> getPassableChildren(SearchNode sn);
void mazeSearch(Maze* maze);

Here are their implementations:

// <...snip...>
inline bool isPassable(Maze* maze, std::pair<int,int> cell) {
  return maze->passable.find(cell) != maze->passable.end();
}


std::vector<SearchNode*> getPassableChildren(SearchNode sn) {
  // Store a cached copy of the children, so if we require multiple queries
  // we do not have to re-compute children.
  if(sn.children.empty()) {
    Maze* mazePointer = sn.pMaze;
    int r = sn.cell.first;
    int c = sn.cell.second;
    for(int i = 0; i <= 2; ++i) {
      for(int j = 0; j <= 2; ++j) {
        if (!(i == 1 && j == 1)) {
          std::pair<int,int> childCell(r+i-1, c+j-1);

          if(isPassable(mazePointer, childCell)) {
            // Build child SN
            SearchNode child;
            child.cell = childCell;
            child.parent = &sn;
            child.pMaze = mazePointer;
            sn.children.push_back(&child);
          }
        }
      }
    }
  }
  return sn.children;
}

void mazeSearch(Maze* maze) {
  std::set<std::pair<int,int> > visited;
  std::deque<SearchNode> workQueue;

  // Create root node.
  SearchNode root;
  root.cell = maze->start;
  root.parent = NULL;
  root.pMaze = maze;

  workQueue.push_back(root);
  visited.insert(root.cell);

  while(!workQueue.empty()) {
    SearchNode sn = workQueue.front();
    workQueue.pop_front();

    for(SearchNode* passableNeighbor : getPassableChildren(sn))  {
      // THIS IF-STATEMENT IS BROKEN
      if(passableNeighbor->cell.first == maze->goal.first &&
         passableNeighbor->cell.second == maze->goal.second) {
        printf("Found a path.\n");
        return;
      }
      // Check to make sure it is not in our visited set.
      // THIS STATEMENT IS ALSO BROKEN
      if (visited.find(passableNeighbor->cell) == visited.end()) {
        workQueue.push_back(*passableNeighbor);
        visited.insert(passableNeighbor->cell);
      }
    }
  }
  printf("No path found.\n");
}
// <...snip...>

The code compiles fine under GCC 4.6.3: $g++ maze.cc -g -std=c++0x However, $./a.out smallMaze.txt produces

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc

I've done some sanity checking with Valgrind and GDB: Valgrind points out that Conditional jump or move depends on uninitialised value(s) in the line that begins

if(passableNeighbor->cell.first == maze->goal.first

and the line nearby that does a set lookup,

if(visited.find(passableNeighbor->cell) == visited.end())

When I inspect these passableNeighbor pointers in GDB, it does look like the underlying SearchNode object hasn't had it's child cell initialized properly, with all sorts of weird values cropping up. I suspect that this has to do with my lack of understanding of how C++ allocates objects.

So it's pretty clear that the underlying issue is that the passableNeighbor object somehow has corrupt data in it. Is this an artifact of how I wrote the getPassableChildren() method? Any other thoughts?

I've looked around at std::bad_alloc and it seems like this exception is usually related to running out of memory, but I'm getting this error on my very first node expanded during BFS, so it seems extremely unlikely that I'm hitting any memory limit.

Was it helpful?

Solution

This part has a problem

      if(isPassable(mazePointer, childCell)) {
        // Build child SN
        SearchNode child;
        child.cell = childCell;
        child.parent = &sn;
        child.pMaze = mazePointer;
        sn.children.push_back(&child);
      }

in that it fills the children with pointers to a local variable. When you leave the if-statement, all the pointers are invalid.

If you create a new child here, you have better store its value than store a pointer.

OTHER TIPS

You are adding to the children vector the address of a local variable, a big no-no

SearchNode child;
child.cell = childCell;
child.parent = &sn;
child.pMaze = mazePointer;
sn.children.push_back(&child);

Use some sort of allocation, or make your children be a vector<SearchNode>

E.g:

SearchNode *child = new SearchNode();
child->cell = childCell;
child->parent = &sn;
child->pMaze = mazePointer;
sn.children.push_back(child);

Then you will need to clean this up later, or make your vector vector<unique_ptr<SearchNode>> and push on unique_ptr<SearchNode>(child) and the de-allocation will be done for you

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top