Hidden segfaults

Started by
15 comments, last by LackOfGrace 16 years, 2 months ago
Quote:Original post by jyk
Your basic implementation of the basic_string class template (such as those provided along with GCC or VC++) should be, for all practical purposes, bug-free. As such, it's safe to say that the string class works fine. If you found that it always made your program crash, it was most likely that you were using it incorrectly. (There aren't that many ways to screw up when using string objects, but it's still possible - for example, by reading or writing past the end of its allocated buffer.)

As an aside to this point, I've had a number of crashes in the standard string class, but it was always because I had made a mistake somewhere else and stepped on the string memory (or something along those lines). It seems like it's unusually sensitive to memory management mistakes. Of course I consider that a good thing since it lets me know if there's a problem.
Advertisement
I agree that if this is supposed to be C++ then it should be done with standard strings, streams, and containers. Either way, it helps to know your way around a debugger.

It only took a few minutes to build your provided code and step through it to see where the problem lies. Looking at the call stack I see it blows up on this line:

rmap[posy][posx].posx = posx;

Most likely posy or posx are out of bounds. The debugger shows
posx = 0x0000000a and posy = 0xcccccccc (probably their initial uninitialized values).
They should have both been set in your sscanf call:
sscanf(params, "%d - %d - %d - %s", &posx, &posy, &pass, sprfile);

According to your data file params should contain the string:
10 - 10 - 0 - resources/sprites/church_floor.bmp
However, the debugger once again shows that it contains only the string:
10

This leads us to where the whole cascading failure really began with the fscanf call:
fscanf(roomfile, "%s = %s", tag, params)
If you look up the documentation for the formatting parameters of fscanf, it will tell you that %s only parses a string up to the next whitespace character.

Remove the whitespaces from your data file, e.g.:
WALL = 10-10-0-resources/sprites/church_floor.bmp
WALL = 10-10-0-resources/sprites/church_floor.bmp

and change your sscanf to not expect the whitespaces:
sscanf(params, "%d-%d-%d-%s", &posx, &posy, &pass, sprfile);

That should make your code actually behave in the manner you're expecting. However, it doesn't address the fact that it's bad code (if it's supposed to be C++) and prone to all sorts of nasty overflows and other bugs.
Quote:Original post by archaos90
                        char spritefile[50];          public:       Room(const char *file);                        void LoadRoom(const char *file);Room::Room(const char *file)void Room::LoadRoom(const char *file)     FILE *roomfile;     char sprfile[50];     roomfile = fopen(file, "r");     char tag[20];     char params[100];     while(fscanf(roomfile, "%s = %s", tag, params) != EOF)              if (strcmp(tag, "WALL") == 0)                       sscanf(params, "%d - %d - %d - %s", &posx, &posy, &pass, sprfile);                       strcpy(rmap[posy][posx].spritefile, sprfile);               else if (strcmp(tag, "COMMENT") == 0)


With code like this, I'm not surprised you have a segfault somewhere. I understand from things like the keyword 'class' that you are trying to write in this new-fangled language called "C++". Have you heard that we have this class that represents a string, called std::string, and a whole new I/O system built on "streams" which are type-safe and work seamlessly with user-defined types? No? Well, the language was only most recently standardized about ten years ago, so...

Not to mention, there is normally no point in factoring out a constructor's work to a separate function like that (don't "reset" a room, just construct another one), no point in an empty else-if block, and it's trivial to convert an integer to a boolean without conditional logic. It's also strange to include "Room" in the name of "Room"'s member functions - do you put wallet money in your wallet? Oh, and a "class" that has everything public is simply a "struct"; C++ does not distinguish those except for private/public considerations.

Oh, and you probably don't want to load the image from the hard drive, every time you draw each tile of the room. Instead, make an associative mapping of sprite file names to the loaded contents. This means you only open each file once. Fortunately, C++ makes this easy, with another helpful gadget called std::map.

#include <string>#include <fstream>#include <sstream>#include <map>struct Sector {  int posx;  int posy;                          std::string spritefile;  bool passable;};std::map<std::string, SDL_Surface*> sprite_mapping;struct Room {  Room(const std::string& filename);  void Draw(SDL_Surface *to);  int rootx;  int rooty;  Sector rmap[100][100];                                                                     };Room::Room(const std::string& filename) {                                ifstream file(filename.c_str()); // the "open" is implicit.  std::string line;  while (std::getline(file, line)) {    // There's this other thing that also exists in C called    // "scoping your variables properly"...    std::stringstream ss(line); // what we use to "re-parse" data.    // Instead of having a whole suite of s* functions that mirror the f*    // file I/O, we simply make a different kind of stream (from the string),    // and then forget it's anything different at all. Polymorphism!    std::string tag;    char equals;    ss >> tag >> equals;    if (equals == '=' && tag == "WALL") { // Yes, you really can compare strings just like that!            // We can avoid repeating ourselves, with a reference:      Sector& s = rmap[posy][posx];      // Yes, we use it like a value, even though it's not a copy...       // ... and we can read directly into its members instead of temporaries:      ss >> s.posx >> s.posy >> s.pass >> s.spritefile;      // Notice how we read into a bool, too. There's no magic format specifier      // for that.      // While we're at it, let's do that loading:      std::string& name = s.spritefile;      if (sprite_mapping.find(name) == sprite_mapping.end()) {        SDL_Surface* sprite = SDL_LoadBMP(name);        SDL_SetColorKey(sprite, SDL_SRCCOLORKEY,                         SDL_MapRGB(sprite->format, 0x00, 0x00, 0xFF));        sprite_mapping[name] = sprite;      } // Otherwise, it was already loaded.    }  }}void Room::Draw(SDL_Surface *surface) {  SDL_Rect pos;  // C++ also lets you declare a counter variable directly in the for loop.  // I think C99 does, too.  for (int i2 = 0; i2 < 100; ++i2) {    for (int i = 0; i < 100; ++i) { // spaces are your friends...      const int TILESIZE = 50; // as are constants.      Sector& s = rmap[i2];      pos.x = s.posx * TILESIZE;      pos.y = s.posy * TILESIZE;      pos.w = TILESIZE;      pos.h = TILESIZE;      std::map<std::string, SDL_Surface*>::iterator it = sprite_mapping.find(s.spritefile);      if (it != sprite_mapping.end()) {        SDL_Surface* sprite = it->second;        SDL_BlitSurface(sprite, &sprite->clip_rect, surface, &pos);      }    }  }}


Of course, it's quite possible that your real problem is that you don't actually have 10,000 Sectors to read in from your file, and therefore you try to draw Sectors that aren't properly set up. The code illustrated will neatly sidestep that (although you will probably run into other issues), by simply trying to find an empty-string spritefile name (because the std::string will get default-constructed, while a char[50] buffer will simply be left to contain whatever was already there in memory), observing that we don't have a corresponding SDL_Surface* loaded, and skipping that Sector.
Quote:Original post by Zahlman
Quote:Original post by archaos90
                        char spritefile[50];          public:       Room(const char *file);                        void LoadRoom(const char *file);Room::Room(const char *file)void Room::LoadRoom(const char *file)     FILE *roomfile;     char sprfile[50];     roomfile = fopen(file, "r");     char tag[20];     char params[100];     while(fscanf(roomfile, "%s = %s", tag, params) != EOF)              if (strcmp(tag, "WALL") == 0)                       sscanf(params, "%d - %d - %d - %s", &posx, &posy, &pass, sprfile);                       strcpy(rmap[posy][posx].spritefile, sprfile);               else if (strcmp(tag, "COMMENT") == 0)


With code like this, I'm not surprised you have a segfault somewhere. I understand from things like the keyword 'class' that you are trying to write in this new-fangled language called "C++". Have you heard that we have this class that represents a string, called std::string, and a whole new I/O system built on "streams" which are type-safe and work seamlessly with user-defined types? No? Well, the language was only most recently standardized about ten years ago, so...

Not to mention, there is normally no point in factoring out a constructor's work to a separate function like that (don't "reset" a room, just construct another one), no point in an empty else-if block, and it's trivial to convert an integer to a boolean without conditional logic. It's also strange to include "Room" in the name of "Room"'s member functions - do you put wallet money in your wallet? Oh, and a "class" that has everything public is simply a "struct"; C++ does not distinguish those except for private/public considerations.

Oh, and you probably don't want to load the image from the hard drive, every time you draw each tile of the room. Instead, make an associative mapping of sprite file names to the loaded contents. This means you only open each file once. Fortunately, C++ makes this easy, with another helpful gadget called std::map.

*** Source Snippet Removed ***

Of course, it's quite possible that your real problem is that you don't actually have 10,000 Sectors to read in from your file, and therefore you try to draw Sectors that aren't properly set up. The code illustrated will neatly sidestep that (although you will probably run into other issues), by simply trying to find an empty-string spritefile name (because the std::string will get default-constructed, while a char[50] buffer will simply be left to contain whatever was already there in memory), observing that we don't have a corresponding SDL_Surface* loaded, and skipping that Sector.


Thanks alot! Really, I had totally forgotten about the C++ streams and it's magic. My code looks as it does, since the really first language I learned was Perl, so, I hope you understand me :)

Just one thing, I locked my mind for char arrays since SDL uses them, and I have no idea of how to convert a string object into a const char* variable, therefor my use of arrays.
ever checked the documentation about std::string?

checkout std::string::c_str()

( spoiler! it will return a pointer to a char array )
( you also have .data() wich will give you another type of pointer )
Quote:Original post by LackOfGrace
ever checked the documentation about std::string?

checkout std::string::c_str()

( spoiler! it will return a pointer to a char array )
( you also have .data() wich will give you another type of pointer )


I think... I'm falling in love

:)


another tip is to take a look at
std::vector, std::map and std::queue

they are really nice

This topic is closed to new replies.

Advertisement