<Bad PTR> when reading CString from .bin file

Hi all,

We are programming a project to manage a library using MFC. We use linked list to manage the admins and save the data to a .bin file. In the program, when we create a new file every time we build the project, the reading has no problem. But when we only read the file created from the previous build, it cannot read and says <Bad PTR>, and has CXX0030: Error: expression cannot be evaluated. It only happens to the CString, the DOB or the status (which are int type) is read well.

Here is the code of the reading:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool admin::load_list()
{
	//return false if can not load the list
	ifstream file;
	file.open("admin.bin",ios::binary);
	if(!file.good())
	{
		return false;
	}
	int n;   // n is the number of admins in the list
	file.read((char*)&n,sizeof(int));
	
	for(int i=0;i<n;i++)
	{
		member* tem=new member;
		file.read((char*)tem,sizeof(member));
		add_from_file(tem); // add the new admin to the list of the program
	}
	file.close();
	return true;
}




Creating:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void admin:: create_file()
{
	ofstream file;
	file.open("admin.bin",ios::binary);
	if(!file.good())
	{
		return;
	}
	int n=3;
	file.write((char*)&n,sizeof(int));
	member* a=new member;
	a->userName="binhtt\0";  //I tried to put the \0 here just to see if there is any difference
	a->address="abcdef\0";
	a->name="admin\0";
	a->pass="polo\0";
	a->dob.set_day(1);
	a->dob.set_month(1);
	a->dob.set_year(1);
	a->next=NULL;
	a->sex=1;
	a->status=1;
	file.write((char*)a,sizeof(member));
	a=new member;
	a->userName="khanhnp";
	a->address="abcdef";
	a->name="admin";
	a->pass="polo";
	a->dob.set_day(1);
	a->dob.set_month(1);
	a->dob.set_year(1);
	a->next=NULL;
	a->sex=1;
	a->status=1;
	file.write((char*)a,sizeof(member));
	a=new member;
	a->userName="huyld";
	a->address="abcdef";
	a->name="admin";
	a->pass="polo";
	a->dob.set_day(1);
	a->dob.set_month(1);
	a->dob.set_year(1);
	a->next=NULL;
	a->sex=1;
	a->status=3;
	file.write((char*)a,sizeof(member));
	file.close();
}



The struct of the admin
1
2
3
4
5
6
7
8
9
10
11
12
struct member
{
	//this struct is used for admin and librarian
	CString name;
	CString userName;
	CString pass;
	date dob;
	CString address;
	int sex;//1 male 2 female
	int status;		
	member *next;
};



Please help us. Many thanks in advanced. And sorry for my bad English.
Last edited on
You have several memory leaks in your create function. You are newing memory, but not deleting it.

What is a CString? A typedef or a class?
@Zhuge CString is the MFC string class, as std::string is the STL string class.

@franknguyen
The problem is that CString is a class, not a native type, so you cannot treat the member struct as a POD type and just file.write() and file.read() it. You need to be a little more careful about the serialization of your object.

But first, an aside:
You should not be storing the user's password directly. I hope you are storing a proper password hash. http://en.wikipedia.org/wiki/Password#Form_of_stored_passwords

OK, back to serialization.

Keep in mind that it makes no sense to store a pointer in the file, since your program is not placed nor uses memory the same way each time it is run. If you save a pointer, then load it, it does not point to the same thing it did the first time. Even if it did, though, the data it pointed at was never saved, so it was lost.

Hence, the next field is not something you can directly store in the file.

The CString objects are themselves wrappers around a pointer to text (a char array), so directly reading and writing the CString structure looses the textual data that is being addressed. (You can imagine a CString to look something like this:

1
2
3
4
5
6
7
8
class CString:
  {
    char* text;      // somewhere on the heap where the user's text is kept
    size_t length;    // the number of chars filled with meaningful data at *text
    size_t capacity;  // the number of chars available for use at *text
  public:
    ...  // methods
  };

So when you write it to file, you save a useless pointer and some otherwise useful size information, but the actual text is lost.)

Hence, the name, userName, pass, and address fields are not something you can directly store in the file.


So, the question is: How do you store this 'pointed at' textual information?

The answer would be much simpler if the password were not stored as a hash (I would say just to read and write text files), but as it should be a hash that contains potentially any character, we need to stick to binary files and do something a little more careful with the strings. We will need a function that serializes the structure into a string, which we can then write. (We will also need a function to de-serialize the string into a member after we read it.)

The serialization should be pretty simple.

All 'date' and 'int' objects are POD types, so they can be written and read exactly as they are. The 'member*' object we will not write to file, since it is not necessary in the file anyway (read the next section below for more).

That just leaves the 'CString' objects, which we can serialize pretty easily. How? Just add the length to the beginning of the string! As I am not too familiar with MFC, I'll use a std::string here, but you should be able to translate the std::string methods to CString easily enough:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::string serialize_string( const std::string& s )
  {
  std::string result;

  // The first four bytes of the string will store the LENGTH of 's'
  // as a little-endian word
  result.resize( 4 );
  result[ 0 ] =  s.length()        & 0xFF;
  result[ 1 ] = (s.length() >>  8) & 0xFF;
  result[ 2 ] = (s.length() >> 16) & 0xFF;
  result[ 3 ] = (s.length() >> 24) & 0xFF;

  // The rest of the string is exactly the same as 's'
  result.append( s );

  // That's it!
  return result;
  }

Now, to serialize a member, you do very much the same thing: create a string, append the serialize_string()ed name, userName, and pass to it, encode the dob manually into it, add the serialize_string()ed address, encode the sex and status. Finally, serialize_string() your total string and write it to file.

To read a member, you just need to go backwards. This is where the fancy serialize_string() stuff pays off. Read the length stuff we put there from the file:

1
2
3
4
5
6
7
8
9
size_t unserialize_length( ifstream& file )
  {
  size_t result = 0;
  result |= file.get();
  result |= file.get() <<  8;
  result |= file.get() << 16;
  result |= file.get() << 24;
  return result;
  }

Now we know exactly how much string to get. :-)

1
2
3
4
5
6
7
8
9
10
std::string unserialize_string_from_file( ifstream& file )
  {
  size_t length = unserialize_length( file );
  char* buffer = new char[ length + 1 ];
  file.read( buffer, length );
  buffer[ length ] = '\0';
  std::string result( buffer );
  delete [] buffer;
  return result;
  }

Etc.

n and member* next

Forget n and next. Treat the file as simply a list of members. The n of members, and their order, are simply the number of members in the file, ordered exactly as they are in the file, first to last. Obvious, no?

Writing the file then becomes something nice

    file.open
    for (member* m = first; m != NULL; m = m->next)
    {
      string binary_data = serialize *m
      file.write binary_data
    }
    file.close

Reading the file also becomes something nice, though a little more bookkeepping is involved to construct the linked list as we go:

    file.open
    member* m = NULL;
    string binary_data
    while (file.read binary_data)
    {
      if (m == NULL) first = m = new member;
      else { m->next = new member; m = m->next; }
      n += 1;
      deserialize binary_data into *m
    }
    m->next = NULL;
    file.close

Phew. Hope this helps. (This is all just off the top of my head, and was designed to be readable, not blazingly efficient. But once you get the right ideas, you can run with them. :O)
To serialize the data better use MFC own CArchive class.
Thanks a lot, especially to Duoas, you guys rock. :D
Topic archived. No new replies allowed.