Solved

C++ Binary File I/O Question

Posted on 2004-04-30
15
332 Views
Last Modified: 2012-08-13
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;
}
0
Comment
Question by:prain
  • 4
  • 3
  • 2
  • +2
15 Comments
 
LVL 16

Expert Comment

by:nonubik
ID: 10959663
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();
}
0
 

Author Comment

by:prain
ID: 10959880
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;
}
0
 
LVL 16

Expert Comment

by:nonubik
ID: 10960118
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);
0
 

Author Comment

by:prain
ID: 10960279
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;
}
0
 
LVL 2

Expert Comment

by:guntherothk
ID: 10960310
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.
0
 

Author Comment

by:prain
ID: 10960414
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
0
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
LVL 2

Expert Comment

by:guntherothk
ID: 10960773
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.
0
 

Author Comment

by:prain
ID: 10960949
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

0
 
LVL 4

Expert Comment

by:havman56
ID: 10962690


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.




0
 
LVL 2

Accepted Solution

by:
guntherothk earned 50 total points
ID: 10964652
I've given you the answer. You don't like it because it's not easy, but that's not my fault. It's not C++'s fault either. You chose to use a library class that dynamically allocates memory because it makes certain things simple. You must now pay for that choice. Had you instead chosen fixed length char arrays, you would have had a perfect experience with read() and write(), and perhaps more difficulty someplace else.

If C++ stored the strings in the same contiguous memory as the class instance, then making a string longer would move all the pointers in the instance around, and would require all class instances to be allocated on the heap, all of which would be expensive at run time. C++ gives you the power to do things efficiently if you want to, or expressively if you want to, but you have to accept the consequences of the tradeoffs, instead of blaming the tool.

Although the standard library data types come with every conforming C++ compiler, they are not all implemented identically, and are not understood as part of the core language. They're add-ons exactly on a par with code you write as far as the compiler is concerned. There are languages (C# comes to mind) that will serialize string data for you, but you pay a big price at runtime to have your language interpreted, and you have to declare all your data public, which goes against well established software engineering principles.

We write serialization methods in C++. Your class would have a method called serialize that takes an ostream& as argument and write()'s each important member. In a big program, if you are fastidious, you don't have to work very hard because you already have written serialization ostream inserters for the fundamental types like int and float. There's lots of ways to do this. Here's a simple-to-understand one

void serialize(ostream& os, int i)
{
    os.write((char*)&i, sizeof(i)); // example, do this for all fundamental types.
                                                // use templates if you're compiler is modern enough
}

// same for other fundamental types

void  classname::serialize(ostream& os)
{
    serialize(os, member1);
    serialize(os, member2);
    ...
    serialize(os, memberN);
}

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.

binarystream& classname::operator<< (binarystream& os)
{
    return os << member1 << member2 << ... << memberN;
}

To make this work you'll have to go to the trouble to understand how ostream inserters work, which is not completely intuitive, but there's lots of good books. I'd recommend The C++ Standard Template Library, by
P.J. Plauger, Alexander A. Stepanov, Meng Lee, David R. Musser.

You have one time to define how to save a binary representation of a std::string, which probably consists of 4 bytes of length and N bytes of character data. After that, in a big program it's pretty easy. And C++ retains the ability to do things efficiently if you don't need variable length, dynamically allocated strings. It's a pretty good tool compared to Java :-).
0
 
LVL 3

Expert Comment

by:CoolBreeze
ID: 10974671
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.
0
 
LVL 3

Expert Comment

by:CoolBreeze
ID: 10974673
i meant, not really go into the depth of "serialization."
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
C++ Properties One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property (http://www.experts-exchange.com/Programming/Languages/CPP/A_3912-Object-Properties-in-C.ht…
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

24 Experts available now in Live!

Get 1:1 Help Now