Reputation: 7406
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
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