Link to home
Start Free TrialLog in
Avatar of prain
prainFlag for United States of America

asked on

C++ Binary File I/O Question

Here is a small program that will write 3 objects into a binary file and then read them back. If you look at the code, I have first defined a class named status in which I have a name field (string type). Then I write three records. Read them back. I see the name of the first object reads properly but the other two do not. However if I change the name field to a char*, it works file. What's the deal?


#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <string>
using namespace std;

#define NUMRECS 3

class status
{
public:
 string name; float balance; unsigned long account_neme;
};

int main(int argc, char *argv[])
{
//Open a file for output
ofstream bal_file_out ("balance.dat");

status write_in, read_back;


//Check before writing
if(!bal_file_out)
      {
      cout << "Can not open file for writing" << endl; return 1;
      }

//Write three records
write_in.name = "AAA\0"; write_in.balance = 1000.0f; write_in.account_neme=25521;
bal_file_out.write(( char *)&write_in,sizeof(write_in));
write_in.name = "BBB\0"; write_in.balance = 2000.0f; write_in.account_neme=73626;
bal_file_out.write(( char *)&write_in,sizeof(write_in));
write_in.name = "CCC\0"; write_in.balance = 5000.0f; write_in.account_neme=57733;
bal_file_out.write(( char *)&write_in,sizeof(write_in));

//close the file.
bal_file_out.close();


//Open the same file for reading
ifstream bal_file_in("balance.dat"); //,ios::in);

//Check if everything was OK before reading
if(!bal_file_in)
      {
      cout << " CAN not open the file for reading" << endl; return 1;
      }

//Read the records and print
for(int y=0;y<NUMRECS;y++)
{
 read_back.name = ""; read_back.balance = 0.0f; read_back.account_neme=0;
 bal_file_in.read(( char *)&read_back,sizeof(read_back));


 cout << "NAME : "<< read_back.name<<endl;
 cout <<"ACCT #; " <<read_back.account_neme <<endl;
 cout.precision(2);
 cout.setf(ios::fixed);
 cout << " Balance : $" << read_back.balance <<endl;
}

//Close the file.
bal_file_in.close();
return 0;
}
Avatar of nonubik
nonubik

The deal should be the using of sizeof.
You should implement a method in your class that gives you the right size of your object. Like:

size_t status::GetSize()
{
  return sizeof(balance) + sizeof(account_neme) + name.size();
}
Avatar of prain

ASKER

Ok, I changed that. But now I am getting reference memory exception when running the program.

#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <string>
using namespace std;

#define NUMRECS 3

class status
{
public:
 string name; float balance; unsigned long account_no;
 size_t getSize() { return (sizeof(balance) + sizeof(account_no) + name.size());}
};

int main(int argc, char *argv[])
{
//Open a file for output
ofstream bal_file_out ("balance.dat");

status write_in, read_back;


//Check before writing
if(!bal_file_out)
      {
      cout << "Can not open file for writing" << endl; return 1;
      }

//Write three records
write_in.name = "AAA\0"; write_in.balance = 1000.0f; write_in.account_no=25521;
bal_file_out.write(( char *)&write_in,sizeof(write_in));
write_in.name = "BBB\0"; write_in.balance = 2000.0f; write_in.account_no=73626;
bal_file_out.write(( char *)&write_in,sizeof(write_in));
write_in.name = "CCC\0"; write_in.balance = 5000.0f; write_in.account_no=57733;
bal_file_out.write(( char *)&write_in, write_in.getSize()) ; //sizeof(write_in));

//close the file.
bal_file_out.close();


//Open the same file for reading
ifstream bal_file_in("balance.dat"); //,ios::in);

//Check if everything was OK before reading
if(!bal_file_in)
      {
      cout << " CAN not open the file for reading" << endl; return 1;
      }

//Read the records and print
for(int y=0;y<NUMRECS;y++)
{
 read_back.name = ""; read_back.balance = 0.0f; read_back.account_no=0;
 bal_file_in.read(( char *)&read_back,read_back.getSize());


 cout << "NAME : "<< read_back.name<<endl;
 cout <<"ACCT #; " <<read_back.account_no <<endl;
 cout.precision(2);
 cout.setf(ios::fixed);
 cout << " Balance : $" << read_back.balance <<endl;
}

//Close the file.
bal_file_in.close();
return 0;
}
Due to the fact that your 'status' object size may vary, you don't know how much data to read. So I suggest to write the size, too


>bal_file_out.write(( char *)&write_in,sizeof(write_in));
should be replaced by
>size_t  theSize = write_in.getSize();
>bal_file_out.write(( char *)&theSize,sizeof(theSize));
>bal_file_out.write(( char *)&write_in,write_in.getSize());

and
>bal_file_in.read(( char *)&read_back,read_back.getSize());
by
>//size_t  theSize;
>bal_file_in.read(( char *)&theSize,sizeof(theSize));
>bal_file_in.read(( char *)&read_back,theSize);
Avatar of prain

ASKER

No. It does not work. Of course the error is gone now. But worst than the original, because now the numbers are all zeros.

Here is what I did...
For writing ......

write_in.name = "AAA\0"; write_in.balance = 1000.0f; write_in.account_no=25521;
size_t theSize = write_in.getSize();
bal_file_out.write((char *) &theSize, sizeof(theSize));
bal_file_out.write(( char *)&write_in,theSize);


write_in.name = "BBB\0"; write_in.balance = 2000.0f; write_in.account_no=73626;
theSize = write_in.getSize();
bal_file_out.write((char *) &theSize, sizeof(theSize));
bal_file_out.write(( char *)&write_in,theSize);

write_in.name = "CCC\0"; write_in.balance = 5000.0f; write_in.account_no=57733;
theSize = write_in.getSize();
bal_file_out.write((char *) &theSize, sizeof(theSize));
bal_file_out.write(( char *)&write_in, theSize) ; //sizeof(write_in));


And for reading....

size_t readSize;
for(int y=0;y<NUMRECS;y++)
{
 read_back.name = ""; read_back.balance = 0.0f; read_back.account_no=0;
 bal_file_in.read((char *) &readSize, sizeof(readSize));
 bal_file_in.read(( char *)&read_back,readSize);


 cout << "NAME : "<< read_back.name<<endl;
 cout <<"ACCT #; " <<read_back.account_no <<endl;
 cout.precision(2);
 cout.setf(ios::fixed);
 cout << " Balance : $" << read_back.balance <<endl;
}
What is the type of the name? It's string. String is a pointer to dynamically allocated memory. When you write your status class, you're writing the four byte pointer, not the string. You have two choices. Either use an array of char for your name, and keep using read() and write() to write out the simple structure in memory, or write the structure one component at a time. To write the string, you'll have to write the length of the string, then the bytes of the string. To read it back in, you'll read the length, preallocate that length, then read the bytes in.

This looks like a homework project, so I've chosen not to give code.
Avatar of prain

ASKER

gun,

Looks like you shot me down right there. This is not a homework assignment. I am a working person. I am in the process of converting some old C code to C++. I just pulled out an example from the net which looks like a homework assignment. I am not posting my actual code I work with, b'cause I am working in a sensitive environment where I am prohibited to post the code.

well going back to the problem.... did you run the code I have given here. Your logic may be right -  which I fully understand, if that is the case why is that it works for the first record (object) and not for the second, and third, and so on.... Try the original code I have posted at the very top.

-prain
No for 50 points I didn't run the code. I have no idea why it works even one time. Perhaps the first pointer refers to the last structure you prepared for writing, which should still be valid.

You can look at the implementation of string, which is std::basic_string<char> in your standard library. The implementation is very probably a pointer to the heap. The reference memory exception is a big hint that you're setting a pointer into some random unmapped address.
Avatar of prain

ASKER

Reference memory exception appeared after I modified the code based on a recommendation by nonunik - which obviously did not work. That's fine if you cannot run the code. But my original code (as I have said the last comment) is at the very top - in the first post.

I gave 50 points becase I thought this could be easy for the "Experts" here. Seems like even the "Experts" are
dumbfounded with this "looks simple problem". I am actually dishartened why we should be going through all this in C++. Given the object, no matter what is inside, it should dump the contents of the object to a given stream.  When comes to C++ string type we simple define a string like....

  string name = "john";

So if that is the case, why cannot we read/write a string to a stream without handling addresses?.

Actually in that way, Java is gold.

Anyways just my openion. Please do not take this an insult to you. I did not mean that.

Thanks
-prain



use setpos() or seek()  ur problem is solved.

1. first see whether 3 records are presnt in the binary file.

2. bal_file_in.read(( char *)&read_back,read_back.getSize());

whenevr u read the file the file poinetrs is pushed that is gptr() is moved in the stream . use setpos or seek()to solve the problem.




ASKER CERTIFIED SOLUTION
Avatar of guntherothk
guntherothk

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
like what guntherothk says, it is not C++'s fault.

>> Seems like even the "Experts" are dumbfounded with this "looks simple problem".

maybe some, maybe some not. guntherothk for instance is not dumbfounded as far as I can see.

>> Given the object, no matter what is inside, it should dump the contents of the object to a given stream.

there are libraries, especially persistence libraries that actually does do that, or those serialization stuff. What C++ gives us is the flexibility and power, if we want the ease, we can just wrap it around with many many layers of stuff such that there is only those stuff we want exposed.

>> So if that is the case, why cannot we read/write a string to a stream without handling addresses?.

what do you mean handle addresses? ideally we don't. when you serialize, you don't usually want to handle addresses either.

>> Actually in that way, Java is gold.

Java is good, I agree. The design is good too. But each language has their own pros and cons.

>> I have no idea why it works even one time. Perhaps the first pointer refers to the last structure you prepared for writing, which should still be valid.

yes, indeed. it is still in the same process, so that object shouldn't have been freed yet.

>> You can derive a stream type from ostream and override the stream inserter methods too, in which case you have something that looks much cooler.

the easy way out here, is that you don't have to do that. nor really serialize. since he is using a filestream, we can just write an overloaded function for operator>> and operator<< based on ofstream's parents.

e.g.

ostream& operator<< (ostream& output, const status& val) {
  return output << val.name << " " << val.balance << " " << val.account_neme;
}

istream& operator>> (istream& input, status& val) {
  getline(input, val.name, ' ');
  input >> val.balance >> val.account_neme;  
  return input;
}

so that he can do something like:

bal_file_out << write_in;

and:

bal_file_in >> read_back;



and yes, this does touches more than an "easy" problem.
i meant, not really go into the depth of "serialization."