2

I am attempting to code a basic game engine in c++/SDL. But while testing my first class, I get memory errors: or Segmentation Faults, or double free or corruption.

It always crashes in the same way, but than the different errors seem to occur at random.

To demonstrate, compile my source code and execute it with as argument the path to a .bmp image file. When you left click, it adds the sprite to the screen on the position your mouse was. when you right click, any sprite in contact with the one on the mouse is supposed to get erased. But instead, it crashes.

I searched a bit/a bit more/a lot more, looked for issues such as deep vs shallow copy for the pointers used, an so on, but without success.

This question is only related in the fact that I am using a dynamic array.

My source, all in one file for your convenience (sorry its around 150 lines, I tried to strip it as much as I could and commented it as much as possible :-/ ):

#include <iostream> //system includes
#include <string>
#include <vector>

#include <SDL/SDL.h> //lib includes

//#include "Tile.h" //project includes

class Tile //-------------------------- TILE.H CONTENT ----------------------
{
    public:
        Tile(const std::string &imgFile, const int &x, const int &y);

        Tile(const Tile &tile);

        void draw(SDL_Surface* dest) const;

        void setPos(const int &x, const int &y);

        bool coliding(const Tile &tile) const;

        virtual ~Tile();
    protected:
    private:
        void init(const std::string &imgFile, const int &x, const int &y); //helper func

        SDL_Surface* m_img;
        SDL_Rect* m_rect;
        std::string m_imgFile; //for deep copying
};

Tile::Tile(const Tile &tile) // ----------------------- TILE.CPP CONTENT -------------------------
{
    init(tile.m_imgFile/*This is to deep copy th img*/, tile.m_rect->x, tile.m_rect->y);
}

Tile::Tile(const std::string &imgFile, const int &x, const int &y)
{
    init(imgFile, x, y);
}

void Tile::init(const std::string &imgFile, const int &x, const int &y)
{
    m_img = SDL_DisplayFormat(SDL_LoadBMP(imgFile.c_str())); // load and optimize sprite
    m_imgFile = imgFile;

    if (!m_img)
    {
        std::cout << "Unable to load image: " << SDL_GetError() << std::endl;
        exit(EXIT_FAILURE);
    }

    m_rect = new SDL_Rect;

    m_rect->h = m_img->h;
    m_rect->w = m_img->w;

    setPos(x, y);
}

void Tile::draw(SDL_Surface* dest) const
{
    SDL_BlitSurface(m_img, 0, dest, m_rect);
}

void Tile::setPos(const int &x, const int &y)
{
    m_rect->x = x;
    m_rect->y = y;
}

bool Tile::coliding(const Tile &tile) const
{
    int x1 = m_rect->x;
    int y1 = m_rect->y;
    int x2 = m_rect->x + m_rect->w;
    int y2 = m_rect->y + m_rect->h;

    int x3 = tile.m_rect->x;
    int y3 = tile.m_rect->y;
    int x4 = tile.m_rect->x + tile.m_rect->w;
    int y4 = tile.m_rect->y + tile.m_rect->h;

    if ((((x1 >= x3 and x1 <= x4) and (y1 >= y3 and y1 <= y4)) or
            ((x2 >= x3 and x2 <= x4) and (y2 >= y3 and y2 <= y4)) or
            ((x1 >= x3 and x1 <= x4) and (y2 >= y3 and y2 <= y4)) or
            ((x2 >= x3 and x2 <= x4) and (y1 >= y3 and y1 <= y4)))
            or
            (((x3 >= x1 and x3 <= x2) and (y3 >= y1 and y3 <= y2)) or
             ((x4 >= x1 and x4 <= x2) and (y4 >= y1 and y4 <= y2)) or
             ((x3 >= x1 and x3 <= x2) and (y4 >= y1 and y4 <= y2)) or
             ((x4 >= x1 and x4 <= x2) and (y3 >= y1 and y3 <= y2))))
        return(true);
    else return(false);

}

Tile::~Tile()
{
    if(m_img != NULL)
    {
        SDL_FreeSurface(m_img);
        m_img = NULL;
    }

    if(m_rect != NULL)
    {
        delete m_rect;
        m_rect = NULL;
    }

    //dtor
}


using namespace std;

int main (int argc, char **argv) //---------------------- MAIN.CPP ------------------------
{
    if(argc != 2) //prog name + arg
    {
        cout << "Needs EXACTLY one argument: file for sprite.\n";
        return EXIT_FAILURE;
    }

    // initialize SDL video
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        cout << "Unable to init SDL: " << SDL_GetError() << endl;
        return EXIT_FAILURE;
    }

    // make sure SDL cleans up before exit
    atexit(SDL_Quit);

    // create a new window
    SDL_Surface* screen = SDL_SetVideoMode(640, 480, 16,
                                           SDL_HWSURFACE|SDL_DOUBLEBUF);
    if ( !screen ) //if screen is a NULL pointer:
    {
        cout << "Unable to set 640x480 video: " << SDL_GetError() << endl;
        return EXIT_FAILURE;
    }

    //create "mouse" sprite and container for the others
    vector<Tile> tiles;
    Tile tile(argv[1], 0, 0);

    // program main loop
    bool done = false;
    bool click_l = false;
    bool click_r = false;

    while (!done)
    {
        // message processing loop
        SDL_Event event;
        while (SDL_PollEvent(&event))
        {
            // check for messages
            switch (event.type)
            {
                // exit if the window is closed
                case SDL_QUIT:
                    done = true;
                    break;

                // check for keypresses
                case SDL_KEYDOWN:
                    {
                        switch(event.key.keysym.sym) //if its a key and its...
                        {
                            case SDLK_ESCAPE: //escape: exit
                                done = true;
                                break;

                            case SDLK_r: //r: empty screen
                                tiles.clear();
                                break;

                            default: //something else: do nothing
                                break;
                        } //end of key switch
                        break;
                    }

                case SDL_MOUSEMOTION: //if the mouse moves
                    tile.setPos(event.motion.x, event.motion.y); //move the mouse tile to it
                    break;

                case SDL_MOUSEBUTTONDOWN: //update click bools
                    if(event.button.button == SDL_BUTTON_LEFT)
                        click_l = true;
                    else if(event.button.button == SDL_BUTTON_RIGHT)
                        click_r = true;
                    break;

                case SDL_MOUSEBUTTONUP://update click bools
                    if(event.button.button == SDL_BUTTON_LEFT)
                        click_l = false;
                    else if(event.button.button == SDL_BUTTON_RIGHT)
                        click_r = false;
                    break;
            } // end switch
        } // end of message processing

        if(click_l) //add a tile on screen if left click
        {
            tiles.push_back(tile);
        }
        else if(tiles.size()>=1 and click_r) //else if right click
        {
            for(size_t i = 0; i < tiles.size(); i++) //go through container
            {
                if(tiles[i].coliding(tile)) //if a tile is coliding with mouse
                {
                    tiles.erase(tiles.begin() + i--); //delete it, decrement i to avoid skiping a tile
                }
            }
        }

        // DRAWING STARTS HERE
        // clear screen
        SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 255, 255, 255));

        // draw bitmaps
        if(tiles.size()>=1) //draw all tiles in conatainer
        {
            for(int i=tiles.size(); i-- > 0;)
            {
                tiles[i].draw(screen);
            }
        }

        tile.draw(screen); //draw tile on mouse
        // DRAWING ENDS HERE

        // finally, update the screen :)
        SDL_Flip(screen);
    } // end main loop

    // if reaches here, no error occured
    cout << endl << "Exited cleanly" << endl;
    return EXIT_SUCCESS;
}

Press r to empty the screen (That works without crashing, for some reason).

Thank you !

Community
  • 1
  • 1
Yk Cheese
  • 120
  • 2
  • 9
  • You have a copy constructor, but no copy assignment. See [What is the Rule of Three?](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – Bo Persson Feb 10 '16 at 16:47
  • Also, it doesn't make sense to set pointers to NULL in the destructor. The object is going away anyway, so all you're doing is wasting cycles. In addition to that, there is no need to check for NULL when issuing a `delete` call. – PaulMcKenzie Feb 10 '16 at 16:49
  • During my research, I read articles saying I should set pointers to NULL after freeing them, but you're right it doesn't make much sense in a destructor – Yk Cheese Feb 10 '16 at 16:52
  • @YkCheese You should have tested how your `Tile` class will hold up in a vector by writing a simple `main` program that copies, assigns, and destroys `Tile`s, checking for double-frees and memory leaks. No need to wait until you use vector to see the problem. Something like this barebones example: http://ideone.com/M6mX7p – PaulMcKenzie Feb 10 '16 at 17:01
  • @BoPersson your advice is correct: It now works. Rewrite it as an answer so I can validate it, please. – Yk Cheese Feb 10 '16 at 17:26
  • @PaulMcKenzie I did do this during my research, but issued something weird so I dropped it. Check it out [here](http://cpp.sh/8gor) – Yk Cheese Feb 10 '16 at 17:30
  • Out of curiosity... if `Tile::m_imgfile` is a `std::string`, and `Tile::init()` takes a `const std::string&`, why does the copy constructor pass it `tile.m_imgfile.c_str()`, but the standard constructor just passes it the constructor's `std::string` argument directly? Correct me if I'm wrong, but wouldn't the second line of `Tile::init()` call `std::string`'s copy constructor and make a deep copy of the string anyways? Just curious. – Justin Time - Reinstate Monica Feb 10 '16 at 18:04
  • @Justin Time ... That would be me coding at 1am. Thanks for pointing it out... – Yk Cheese Feb 10 '16 at 20:01
  • You're welcome. Wasn't sure if there was some benefit I wasn't aware of, or anything. – Justin Time - Reinstate Monica Feb 10 '16 at 20:39

0 Answers0