Spajus
Spajus

Reputation: 7406

C++ pointers and class hierarchy causing a segfault puzzler

I am trying to figure out a root cause of a segfault behind a small code change. Let's begin with this simplified version of code, which runs without issues:

#include <iostream>
#include <string>
#include <vector>

class GameObject {
    public:
        virtual void update();
        void addChild(GameObject* child);

        GameObject* parent;
        std::vector<GameObject*> children;
        int x;
        int y;
};

class Player : public GameObject {
    public:    
        void growTail();
        void update();
};

class TailNode : public GameObject {
    public:    
        void addTo(GameObject* parent);
        void update(); 
        // UPDATE AFTER ANSWER IS CLEAR: this was in original codebase and it was causing the segfault
        GameObject* parent; 
};

void GameObject::addChild(GameObject* child) {
    this->children.push_back(child);
    child->parent = this; // <- can't do this in full codebase, causes segfault later
}

void GameObject::update() {
     for (GameObject* child : children) {
            child->update();   
     }
}

void Player::update() {
     GameObject::update();
     std::cout << "Player at: [" 
         << std::to_string(x) 
         << std::to_string(y)
         << "]"
         << std::endl;
}

void Player::growTail() {
    TailNode* tail = new TailNode();
    tail->addTo(this);
}

void TailNode::update() {
    GameObject::update();
     std::cout << "Tail parent at: [" 
         << std::to_string(parent->x) // <- if parent is set inside GameObject::addChild, this segfaults in full codebase
         << std::to_string(parent->y)
         << "]"
         << std::endl;  
}

void TailNode::addTo(GameObject* parent) {
     parent->addChild(this);   
     // this->parent = parent; <-- have to do this to avoid segfault in full codebase
}

int main() {
     Player* player = new Player();
     player->x = 10;
     player->y = 11;
     player->growTail();
     player->update();
}

Now, in the game source itself, it does segfault, as apparently tail node gets a bad parent.

When I move child->parent = this; away from GameObject::addChild and use this->parent = parent; at TailNode::addTo, it works.

I am guessing it is related to pointers and the way I am misusing them.

You can find the full working codebase at https://github.com/spajus/sdl2-snake/tree/tail_working And the commit that breaks it: https://github.com/spajus/sdl2-snake/commit/4e92e9d6823420ce7554f2b6d7d19992c48d4acc

I'm compiling and running it on OS X 10.11.5,

Apple LLVM version 7.3.0 (clang-703.0.31)
Target: x86_64-apple-darwin15.5.0
Thread model: posix

Code depends on SDL2 and few of it's extensions.

Example compilation command:

$XCODE/XcodeDefault.xctoolchain/usr/bin/c++ \
-I/Library/Frameworks/SDL2.framework/Headers \
-I/Library/Frameworks/SDL2_image.framework/Headers \
-I/Library/Frameworks/SDL2_mixer.framework/Headers \
-I/Library/Frameworks/SDL2_ttf.framework/Headers \
-I$HOME/Dev/sdl2-snake/include \
-Wall -Wextra -pedantic -std=c++11 \
-Wall -Wextra -pedantic -std=c++11 \
-g -g -o CMakeFiles/Snake.dir/src/main.cpp.o \
-c $HOME/Dev/sdl2-snake/src/main.cpp

I know that you will recommend to use smart pointers instead of naked ones, and you may be right, but here I'm just having fun and am curious to find out why it breaks this way. It may be difficult to reproduce (there are build scripts though), but perhaps an experienced C++ developer with a good eye will spot the problem right away.

Also, I appreciate a code review and improvement suggestions, my C++ skills are close to zero, as I haven't done anything with it since university over a decade ago.

Upvotes: 3

Views: 63

Answers (1)

Spajus
Spajus

Reputation: 7406

Thanks to orbitcowboy's tip, I used cppcheck which found the issue right away:

[include/snake/tail_node.hpp:13] -> [include/snake/game_object.hpp:18]: (warning) The class 'TailNode' defines member variable with name 'parent' also defined in its parent class 'GameObject'.

After removing the redefinition of GameObject* parent from TailNode class, it no longer segfaults.

Commit that fixes the issue: https://github.com/spajus/sdl2-snake/commit/a1808e7d2426b5aedd9ab3a4dc2aa38aa0225a95

Upvotes: 2

Related Questions