Jump to content
  • Advertisement
Sign in to follow this  
StonieJ

VERY simple file i/o question

This topic is 4830 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

It's been a LONG time since I've dealt with non-Win32 file i/o, so I was hoping someone could show me what I'm doing wrong. All I'm trying to do is write a simple program that writes a series of Player objects to a file, then turns around and reads it back in and display them.
#include "stdafx.h"

using namespace std;

struct TPlayer
{
	string name;
	int ammo;
	int health;
	int armor;

	void PrintPlayer()
	{
		cout << "Name:   " << name << endl;
		cout << "Ammo:   " << ammo << endl;
		cout << "Health: " << health << endl;
		cout << "Armor:  " << armor << endl;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	TPlayer player1;
	player1.name = "Blake";
	player1.ammo = 50;
	player1.armor = 200;
	player1.health = 100;

	TPlayer player2;
	player1.name = "Brandon";
	player1.ammo = 75;
	player1.armor = 150;
	player1.health = 87;

	TPlayer player3;
	player1.name = "Emily";
	player1.ammo = 25;
	player1.armor = 120;
	player1.health = 79;

	TPlayer player4;
	player1.name = "Ramin";
	player1.ammo = 60;
	player1.armor = 140;
	player1.health = 56;

	ofstream player_writer("players.dat", ios::out | ios::ate | ios::binary);

	if (player_writer.is_open())
	{
		player_writer.write((const char *)&player1, sizeof(player1));
		player_writer.write((const char *)&player2, sizeof(player2));
		player_writer.write((const char *)&player3, sizeof(player3));
		player_writer.write((const char *)&player4, sizeof(player4));
		player_writer.close();
	}

	ifstream player_reader("players.dat", ios::in | ios::binary);

	int file_size, records;
	TPlayer temp_player;

	if (player_reader.is_open())
	{
		player_reader.seekg(0, ios::end);
		file_size = player_reader.tellg();
		player_reader.seekg(0, ios::beg);

		records = file_size / sizeof(TPlayer);

		for (int i = 0; i < records; i++)
		{
			cout << "i = " << i << ":\tPosition = " << player_reader.tellg() << endl;
			player_reader.read((char *)&temp_player, sizeof(TPlayer));

			temp_player.PrintPlayer();
			cout << endl;
		}

		player_reader.close();
	}

	getch();

	return 0;
}


The problem is that this is what I'm getting when I read the file back in and display the contents:
i = 0:  Position = 0
Name:   Ramin
Ammo:   60
Health: 56
Armor:  140

i = 1:  Position = 40
Name:
Ammo:   -858993460
Health: -858993460
Armor:  -858993460

i = 2:  Position = 80
Name:
Ammo:   -858993460
Health: -858993460
Armor:  -858993460

i = 3:  Position = 120
Name:
Ammo:   -858993460
Health: -858993460
Armor:  -858993460
The last record I write is the first that shows up when it is read, but the last 3 are garbage. Is it a problem with my file writing or reading function? Thanks.

Share this post


Link to post
Share on other sites
Advertisement
	TPlayer player1;
player1.name = "Blake";
player1.ammo = 50;
player1.armor = 200;
player1.health = 100;

TPlayer player2;
player1.name = "Brandon";
player1.ammo = 75;
player1.armor = 150;
player1.health = 87


You don't initialise player2, 3 and 4 but give player 1 new values each time

Share this post


Link to post
Share on other sites
...........

(If you only knew how long I've spent re-writing this damn file code.)

Thanks though...I think it's time for me to go to bed...and find another hobby.

Share this post


Link to post
Share on other sites
Well, maybe I can salvage some of this with a more legitimate question. I would think that a string object would have a variable length, depending on the number of characters that it is storing. However, obviously all of the string objects are the same size, regardless of what character string they hold. Just wondering how that worked. For example:


string name1 = "This is my big-ass name.";
string name2 = "Hi!";

sizeof(name1) = 28
sizeof(name2) = 28
sizeof(string) = 28


Share this post


Link to post
Share on other sites
The std::string object wraps around a char* pointer to the actual string data (which might be variable length). This is necessary because the size of any single object has to be known at compile time, and std::string is an object. (The string's pointer will point at a dynamic, array allocation.)

Basically, std::string is nothing like a "POD type" - i.e. it contains pointers and other such magic - and therefore, writing it directly to file like that is just plain wrong. It only seems to work in your (the OP's) case because many implementations of std::string (including yours apparently) use a static buffer for short items (typically on the order of 16 characters or so), switching over to a dynamic allocation seamlessly when the string gets too big for that buffer. So you end up writing that buffer to file, and a null pointer value, and when you read it all back in, it seems to work. But it's dangerous and very much not what you want.

Because the data *isn't* necessarily of the same length, using sizeof hacks to figure out how many items there are is similarly not a good idea. However, you don't need to store an item count, either, because the standard library also provides an automatically-resizing array that will expand to hold however many items there happen to be: std::vector.

So, back to business. To "serialize" (I'm giving you the fish; learn to fish here) the std::string properly, what we want to output is a length count (as a binary value), followed by the string data (using .data(), not .c_str(), because we're not interested in null termination here). We of course put the length count *first*, and then the data in the file, and then we can tell how far to read. Then after constructing the std::string object, we can deal with the rest of the current struct. In general, you should be dealing with structs member-wise *anyway*, because struct padding could mess you up later down the road (or at least obstruct portability).

(WARNING: not tested - should at least be close though)

#include "stdafx.h"

using namespace std;

// First, we'll pretty up the usual means of writing a bit of binary data
// (i.e. work around what I consider a deficiency in the library :) )

template <typename T>
void writeTo(ostream& os, const T& datum) {
os.write(reinterpret_cast<const char *>(&datum), sizeof(T));
}

template <typename T>
T readFrom(istream& is) {
T result;
is.read(reinterpret_cast<const char *>(&result), sizeof(T));
return result;
}

// Now, our new and improved player struct:
struct TPlayer {
string name;
int ammo;
int health;
int armor;

// We can make the printing a bit more general:
ostream& print(ostream& os) {
os << "Name: " << name << endl
<< "Ammo: " << ammo << endl
<< "Health: " << health << endl
<< "Armor: " << armor << endl;
return os;
}
// We can make creating a player easier:
TPlayer(string name, int ammo, int health, int armor) :
name(name), ammo(ammo), health(health), armor(armor) {}
// since we provided that, we have to explicitly allow for
// "default construction" if we want it:
TPlayer() {}

// And we'll make it so that a player can "write itself" in binary too:
ostream& printBinary(ostream& os) {
// write the name as described before: length, then data.
writeTo<int>(os, name.length());
// We don't use the writeTo for this; it's meant to write single things,
// not arrays. And we don't want to write the pointer value itself :)
os.write(name.data(), name.length());
// And now the other members
writeTo<int>(os, ammo);
writeTo<int>(os, health);
writeTo<int>(os, armor);
}

// And we'll allow for setting up a player by reading in the stream data:
istream& readBinary(istream& is) {
// Find out how long the name is, read that much data, then create a
// std::string with it.
int length = readFrom<int>(is);
char* buffer = new char[length];
is.read(buffer, length);
name = string(buffer, length);
delete buffer; // clean up - todo: exception safety :S
// and now assign the other members.
ammo = readFrom<int>(is);
health = readFrom<int>(is);
armor = readFrom<int>(is);
return is; // lets us check if reading went OK.
}
};

// and we can also make it a bit more friendly:
ostream& operator<<(ostream& os, const TPlayer& player) {
return (os << player);
}

int _tmain(int argc, _TCHAR* argv[]) {
// We'll create a vector of players for output, and make use of our new
// constructor. This will shield us from the kind of copy and paste error
// that was there before :)
vector<TPlayer> players;
players.push_back(TPlayer("Blake", 50, 100, 200));
players.push_back(TPlayer("Brandon", 75, 87, 150));
players.push_back(TPlayer("Emily", 25, 79, 120));
players.push_back(TPlayer("Ramin", 60, 56, 140));

ofstream player_writer("players.dat", ios::out | ios::ate | ios::binary);
if (player_writer.is_open()) {
// Write out each player in the vector.
for (int i = 0; i < players.size(); ++i) {
players.printBinary(player_writer);
}
player_writer.close();
}

// Now we can read everything back in...
ifstream player_reader("players.dat", ios::in | ios::binary);
// Instead of counting the records, we'll just append to the end of the
// vector as needed.
vector<TPlayer> savedPlayers;
TPlayer temp_player;
if (player_reader.is_open()) {
TPlayer temp_player;
int i = 1;
while(temp_player.readBinary(player_reader)) {
// got another player's data in the temp player
savedPlayers.push_back(temp_player); // store it
// And, just to prove the point, output it
cout << "Player #" << i << endl
<< temp_player << endl; // Yep, it becomes that nice for the "client code"
// once the necessary setup work is done.
++i; // only used for labelling purposes :s
}
}
}



[/source]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!