views:

333

answers:

5

IMPORTANT EDIT:

Sorry everyone, i made a big mistake in the structure. char *name; is meant to be outside of the structure, written to the file after the structure. This way, you read the structure, find out the size of the name, then read in the string. Also explains why there is no need for a null terminator. However, i feel somewhere, my actual question has been answered. If someone would like to edit their responses so i can choose one which is the best fitting i'd appreciate it.

Again, the question I was asking is "If you read in a structure, are you also reading in the data it holds, or do you need to access it some other way".

Sorry for the confusion

For an assignment, I've been tasked with a program which writes and reads structures to a disk (using fread and fwrite).

I'm having trouble grasping the concept. Lets say we have this structure:

typedef struct {
    short nameLength;
    char* name;
}attendenceList;

attendenceList names;

now assume we give it this data:

names.name = "John Doe\0";
names.nameLength = strlen(names.name); /*potentially -1?*/

and then we use fwrite... given a file pointer fp.

fwrite(&names,sizeof(names),1,fp);

now we close the file, and open it later to read in the structure. the question is this: when we read in the structure, are we also reading in the variables it stores?

Can we then now do something like:

if(names.nameLength < 10)
{
 ...
}

Or do we have to fread something more then just the structure, or assign them somehow? Assuming the fread is:

fread(&names,sizeof(names),1,fp);

Also assuming we've defined the structure in our current function, as above.

Thanks for the help!

A: 

I'm assuming you meant char* name instead of char name. Also sizeof(name) will return 4 because you are getting the size of a char* not the length of the char array. So you should write strlen(name) not sizeof(name) inside your fwrite.

In your above example I would recommend storing the string exact size without the null termination. You don't need to store the string length as you can get that after.

If you are reading just a string from a file, and you wrote the exact size without the null termination. Then you need to manually null terminate your buffer after you read the data in. So make sure you allocate at least the size of your data you are reading in plus 1.
Then you can set the last byte of that array to '\0'.

If you write a whole struct at a time to the buffer, you should be careful because of padding. The padding may not always be the same.

when we read in the structure, are we also reading in the variables it stores?

Yes you are, but the problem you have is that as I mentioned above you will be storing the pointer char* (4 bytes) and not the actual char array. I would recommend storing the struct elements individually.

Brian R. Bondy
huh? I dont understand what you mean '[my] buffer'?Ah got it thanks, will fix up my code. It was just an example, because i dont want to use anything near the actual assignment.to get the length of a string (its actually from a file, so using fread) would i need to -1 the strlen?
Blackbinary
`strlen` returns the length of the string not including the null termination.
Brian R. Bondy
Thanks for the clarification, however my actual question has remained unanswered.
Blackbinary
@Blackbinary: Updated my answer to answer your exact question.
Brian R. Bondy
+3  A: 

You have a problem here:

fwrite(&names,sizeof(names),1,fp);

Since attendenceList saves the name as a char * this will just write out the pointer, not the actual text. When you read that back in, the memory the pointer is referencing will most likely have something else in it.

You have two choices:

  1. Put a character array (char names[MAXSIZE]) in attendenceList.
  2. Don't write the raw data structure, but write the necessary fields.
R Samuel Klatchko
+1  A: 

You're writing the memory layout of the structure, which includes its members.

You'll get them back if you read the structure back in again - atleast if you do it on the same platform, with a program compiled with the same compiler and compiler settings.

Your name member is declared just as a char, so you can't store a string in it.

If name was a pointer like this:

typedef struct {
    short nameLength;
    char *name;
}attendenceList;

You really should not read/write the struct to a file. You will write the structure as it's laid out in memory, and that includes the value if the name pointer.

fwrite knows nothing about pointers inside your structure, it will not follow pointers and also write whatever they point to.

when you read the structure back again, you'll read in the address in the name pointer, and that might not point to anything sensible anymore.

If you declare name as an array, you'll be ok, as the array and its content is part of the structure.

typedef struct {
    short nameLength;
    char name[32];
}attendenceList;

As always, make sure you don't try to copy a string - including its nul terminator- to name that's larger than 32. And when you read it back again. set yourstruct.name[31] = 0; so you are sure the buffer is null terminated.

To write a structure, you'd do

attendenceList my_list;

//initialize my_list
if(fwrite(&my_list,sizeof my_list,1,f) != 1) {
 //handle error
}

And to read it back again:

attendenceList my_list;

//initialize my_list
if(fread(&my_list,sizeof my_list,1,f) != 1) {
 //handle error
}

}

nos
A: 

You ask:

now we close the file, and open it later to read in the structure. the question is this: when we read in the structure, are we also reading in the variables it stores?

No. sizeof(names) is a constant value defined at compile time. It will be the same as

sizeof(short) + sizeof(void*) + some_amount_of_padding_to_align_things

it will NOT include the size of what names.name points to, it will only include the size of the pointer itself.

So you have two problems when writing this to a file.

  1. you aren't actually writing the name string to the file
  2. you are writing a pointer value to the file that will have no meaning when you read it back.

As your code is currently written, When you read back the names, names.name will point to somewhere, but it won't point to "John Doe\0".

What you need to do is to write the string pointed to by names.name instead of the pointer value.

What you need to do is sometimes called "flattening" the structure, You make a structure in memory that contains no pointers, but holds the same data as the structure you want to use, then you write the flattened structure to disk. This is one way to do that.

typedef struct {
    short nameLength;
    char  name[1]; // this will be variable sized at runtime.
}attendenceListFlat;

int cbFlat = sizeof(attendenceListFlat) + strlen(names.name);
attendenceListFlat * pflat = malloc(cbFlat);
pflat->nameLength = names.nameLength;
strcpy(pflat->name, names.name);

fwrite(pflat, cbFlat, 1, fp);

The flattened structure ends with an array that has a minimum size of 1, but when we malloc, we add strlen(names.name) so we can treat that as an array of strlen(names.name)+1 size.

John Knoeller
A: 

A few things.

Structures are just chunks of memory. It's just taking a bunch of bytes and drawing boundaries on them. Accessing structure elements is just a convenient way of getting a particular memory offset cast as a particular type of data

You are attempting to assign a string to a char type. This will not work. In C, strings are arrays of characters with a NULL byte at the end of them. The easiest way to get this to work is to set a side a fixed buffer for the name. When you create your structure you'll have to copy the name into the buffer (being very careful not to write more bytes than the buffer contains). You can then write/read the buffer from the file in one step.

struct attendanceList {
     int  namelen;
     char name[256]; //fixed size buffer for name
}

Another way you could do it is by having the name be a pointer to a string. This makes what you're trying to do more complicated, because in order to write/read the struct to/from a file, you will have to take into account that the name is stored in a different place in memory. This means two writes and two reads (depending on how you do it) as well as correctly assigning the name pointer to wherever you read the data for the name.

struct attendanceList {
    int   namelen;
    char* name; //the * means "this is a pointer to a char somewhere else in memory"
}

There's a third way you could do it, with a dynamically sized struct using a trick with a zero length array at the end of a struct. Once you know how long the name is, you allocate the correct amount (sizeof(struct attendanceList) + length of string). Then you have it in one contiguous buffer. You just need to remember that sizeof(struct attendanceList) is not the size you need to write/read. This might be a little confusing as a beginning. It is also kind of a hack that's not supported under all compilers.

struct attendanceList {
   int namelen;
   char name[0];  //this just allows easy access to the data following the struct.  Be careful!

}

jdizzle