Understanding binary file io

Hello, I have been writing a little tic tac toe program and in it I made some code to save my "Player" object in binary format to a file so that in the game a player can save his profile and then load his profile later. It is not totally done - but in order to get what saves are available in the file (so that the user can see what saved profiles are available for loading) I call a function that reads through all of the Player objects saved and adds the name (if we have Player p; it adds p.getName(); ) of the Player object to a vector<string> and that's it. I create the vector<string> before calling the funtion and pass the vector in to the function by reference and then the function fills it with names.

here is my function for saving..

void Player::save()
{
std::fstream saveFile;
saveFile.open(fName, std::fstream::binary | std::fstream::app | std::fstream::out );
Player t = *this;
saveFile.write(reinterpret_cast<char *>(&t), sizeof(t));
saveFile.close();
}

fName is a string that contains the name ("blabla.dat") of my savefile that I always use for saving and loading Player objects to.
Also, I wrote and operator= for the Player class to make sure it just copy's all of the members.. it is as follows..

Player& Player::operator=(const Player& rhs)
{
_name = rhs._name;
_myChar = rhs._myChar;
_score = rhs._score;
_wins = rhs._wins;
_losses = rhs._losses;
fName = rhs.fName;
return *this;
}

And here is my function for getting the names of my saved Player objects...

void Player::getSaves(vector<string> & savedProfiles)
{
std::fstream saveFile;
saveFile.open(fName, std::fstream::binary | std::fstream::in);
while (!saveFile.eof() && saveFile)
{
Player t;
saveFile.read(reinterpret_cast<char *>(&t), sizeof(t));
savedProfiles.push_back(t.getName());
}
saveFile.close();
if (savedProfiles.size() == 0)
throw std::runtime_error("No Profile Saves Found");
}

So my problem is this - When I call getSaves my program crashes with an Access violation error...
If I define the function like this

void Player::getSaves(vector<string> & savedProfiles)
{
std::fstream saveFile;
saveFile.open(fName, std::fstream::binary | std::fstream::in);
while (!saveFile.eof() && saveFile)
{
Player * t = new Player;
saveFile.read(reinterpret_cast<char *>(t), sizeof(Player));
savedProfiles.push_back(t->getName());
delete t;
}
saveFile.close();
if (savedProfiles.size() == 0)
throw std::runtime_error("No Profile Saves Found");
}

I still get the same Access Violation Error... So I looked around the internet and what I basically got from most forums was that c++ can't dump and reload class objects to file like - you have to dump all members separately.. So that's all fine I can change my code to do that but the part that drives me nuts is that if I take out the delete t; line the function works completely normally and fills the vector like it should. If I define the function the first way and I defined a destructor that simply prints "Got here" to the screen, when I run it the program crashes with the access violation right after it prints the message "Got Here"... Do you have any idea what in the world is going on??? This is driving me crazy! Please let me know if you need the actual files.. I tried to give you enough information - I think it is more of a conceptual issue than actual syntax.. I think I don't fully understand how c++ is reading my objects from the binary file

Also I know boost has a library for object serialization - I'm not trying to reinvent the wheel I just want to *understand* what is going on rather than just using boost - once I understand then I will use boost for this sort of thing
Show me the data members for Player, and I'll show you why that code isn't working.

EDIT: also, does Player have any virtual functions?
Last edited on
class Player
{
public:
Player(string = "Default");
Player(const Player&);
void setName( string );
Player& operator=(const Player &);
int getScore();
void makeMove(Board &, int);
string getName();
int getWins();
int getLosses();
char getChar();
void addWin();
void setChar(char);
void addLoss();
string toString();
void load(string);
void save();
void getSaves(vector<string> &);
~Player();


private:
string _name;
char _myChar;
int _score;
int _wins;
int _losses;
string fName;
};


here is the header file - you can see all the members here.
Last edited on
Okay you have strings here. That's your problem. Strings are a complex type. As such, you should never write them directly as binary data. Here's the reason why:

string might look something like this:

1
2
3
4
5
6
class string
{
private:
   char* buffer;
   int length;
};


The actual string data is not stored in the string itself. Instead, string just has a pointer that points to the data. Now when you write a string to a binary file, only the pointer gets written. It does not write the actual string data.


Here's a simple way you can test to see what I mean:

1
2
3
4
5
6
7
8
9
10
int main()
{
  string foo = "This is a very long string.";
  foo += "  It will definitely be longer than a few bytes.";

  ofstream myfile;
  myfile.open("file.bin", std::fstream::binary | std::fstream::app | std::fstream::out );

  myfile.write( reinterpret_cast<char*>(&foo), sizeof(foo) );
}


If you do that, you'll notice the file it makes is only a few bytes. The size doesn't change depending on how large the actual string data is.



What's more, when you read this from the file... instead of reading string data... instead you are just reading a pointer. (but that pointer doesn't point to what it used to! So it ends up pointing to nothing/garbage, which makes your program explode).


So instead, you want to read/write the string data, not the string object. There are a few ways to do this. Here's one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void WriteString(ofstream& file,const string& str)
{
  // get the length of the string data
  unsigned len = str.size();

  // write the size:
  file.write( reinterpret_cast<const char*>( &len ), sizeof(len) );

  // write the actual string data:
  file.write( str.c_str(), len );
}

string ReadString(ifstream& file)
{
  // this probably isn't the optimal way to do it, but whatever
  string str;

  // get the length
  unsigned len;
  file.read( reinterpret_cast<char*>( &len ), sizeof(len) );

  // we can't read to string directly, so instead, create a temporary buffer
  if(len > 0)
  {
    char* buf = new char[len];
    file.read( buf, len );
    str.append( buf, len );
    delete[] buf;
  }
  return str;
}
Thank you so much! This was driving me crazy - so it must have been saving the pointer to the _name string which pointed to some random spot in memory allocated by string to the binary file.. then when I loaded this pointer back in to a Player object it still happened to point at the correct name (nothing in the os had overwritten it yet) - but when I tried to delete the Player object the string class destructor tried to delete this random spot in memory which was why I would get an access violation - and also why the object would still work if I didn't delete it.. However if something happened to overwrite that spot later my _name would just contain garbage. That makes me feel soooo much better! Thank you!
Yeah you seem to understand it quite well now!

Glad I could help.
Topic archived. No new replies allowed.