1

I have the following structs in C:

typedef struct sUser {
    char name[nameSize];
    char nickname[nicknameSize];
    char mail[mailSize];
    char address[addressSize];
    char password[passwordSize];
    int totalPoints;
    PlacesHistory history;
    DynamicArray requests;
}User;

typedef struct sPlacesHistory {
    HistoryElement array[HistorySize];
    int occupied;
    int last;
}PlacesHistory;

and the functions:

void serializeUser( User * user, FILE * fp ) {
    fwrite( user, nameSize + nicknameSize + mailSize + addressSize + passwordSize + sizeof( int ) + sizeof( PlacesHistory ), 1, fp );
    serializeDynamicArray( user -> requests, fp );
}

User * loadUser( FILE * fp ) {
    User * user = malloc( sizeof( User ) );
    fread( user, nameSize + nicknameSize + mailSize + addressSize + passwordSize + sizeof( int ) + sizeof( PlacesHistory ), 1, fp );
    user -> requests = loadDynamicArray( fp );

    return user;
}

When I load the struct User, and I print that user (loaded from file), the field "last" of placesHistory has the value of 255 or -1, depending on the order of the fields of the PlacesHistory structure. But The User I saved had -1 on that member.

So when i get 255, it is obviously wrong.. I suspect this has to do about struct padding.

How can I do this in such a way that the order of fields in the structure doesn't matter?
Or which criteria do I need to follow to make things work right?
Do I need to fwrite/fread one member at a time? ( I would like to avoid this for efficiency matters )
Do I need to serialize to an array first instead of a file? (I hope not .. because this implicates to know the size of all my structures beforehand because of the mallocated array- which means extra work creating a function for every non simple structure to know it's size)

Note: *Size are defined constants
Note2: DynamicArray is a pointer to another structure.

jmacedo
  • 773
  • 1
  • 13
  • 24
  • The obvious thing would be to just read/write the whole struct, `fwrite(user,sizeof *user,1,fp)` Is there any reason you can't do that ? If you want the order of fields to not matter, that's a whole other story, and you need to use some kind of structured file format to accomplish that. – nos Apr 05 '11 at 19:10
  • @nos: OP probably didn't do it because `requests` is not supposed to be read. The solution for that would be to have two structs, one with the data to be read, and one master struct with this *and* `requests`. – EboMike Apr 05 '11 at 19:16
  • In that case, calculate the size of the struct with the offsetof() macro, to exclude the last element instead of using sizeof. – nos Apr 05 '11 at 19:26
  • cant do fwrite( user, sizeof( User ), 1 fp ) because requests is a pointer type... I need to use the serializeDynamicArray function do seralize that stuff. – jmacedo Apr 05 '11 at 19:29

3 Answers3

1

Yes, it probably has to do with padding in front of either totalPoints or history.

You can just write out sizeof(User) - sizeof(DynamicArray) and read back in the same. Of course this will only be compatible as long as your struct definitions and compiler don't change. If you don't need serialized data from one version of your program to be compatible with another version, then the above should work.

rlibby
  • 5,931
  • 20
  • 25
  • @joxnas, @nos pointed out `offsetof()` which is probably a better approach to the same concept. The use in your case would be `fwrite(user, offsetof(User, requests), 1, fp)`. Both suggestions work because your `DynamicArray` member is the last one. The `offsetof` approach is more flexible and can be made to work when you want to leave out a member that is not the final one. – rlibby Apr 05 '11 at 19:42
  • ok, thanks for the tip. Explain me just one more thing: if requests wasn't the last member, let's suppose I changed the order switching requests with mail. How would I use offsetof() now? – jmacedo Apr 05 '11 at 22:27
  • 1
    @joxnas, `fwrite(user, offsetof(User, requests), 1, fp); serializeDynamicArray(user->requests, fp); fwrite(&user.address, sizeof(User) - offsetof(User, address), 1, fp);` – rlibby Apr 06 '11 at 02:48
  • thank you all! i'll vote useful or/and informative answers up when I get the reputation needed. – jmacedo Apr 06 '11 at 20:27
0

Why are you adding up all elements individually? That's just adding a lot of room for error. Whenever you change your structure, your code might break if you forgot to change all the places where you add the size up (in fact, why do you add it up each time?).

And, as you suspected, your code doesn't account for structure padding either, so you may be missing up to three bytes at the end of your data block (if your largest element is 4 bytes).

Why not sizeof(User) to get the size of the amount of data you're reading/writing? If you don't want parts of it saved (like requests), then use a struct inside a struct. (EDIT: Or, like rlibby suggested, just subtract the sizeof of the part you don't want to read.)

My guess is that your strings sizes are not divisible by 4, so you are 3 bytes short, and as such, it's possible that you were supposed to read "0xffffffff" (=-1) but ended up just reading "0xff000000" (=255 when using little endian, and assuming that your structure was zeroed out initially).

EboMike
  • 76,846
  • 14
  • 164
  • 167
0

padding may be your problem because

nameSize + nicknameSize + mailSize + addressSize + passwordSize + sizeof( int ) + sizeof( PlacesHistory ) != sizeof( User ) 

so la last member (and last in struct) remain unitialized. To check this do a memset(,0,sizeof(User)) before reading from file.

To fix this use #pragma pack(push,1) before and #pragma pack(pop) after

cprogrammer
  • 5,503
  • 3
  • 36
  • 56
  • #pragma pack is a very bad idea. Misaligned integers can cause a severe performance impact, or even an outright crash (like on PowerPC). – EboMike Apr 05 '11 at 22:34
  • performance impact - yes, but why crash ?. and it's the only option to read/write struct from a file. otherwise if you change compiler you will have troubles ... Maybe not aligned to 1 byte but, alignment it's a must, not an option – cprogrammer Apr 06 '11 at 06:16
  • PowerPC *has* to read ints from int-aligned addresses. If you try to read a 4-byte int from an address like 0x80000001 (i.e. not divisible by 4), it will crash. – EboMike Apr 06 '11 at 06:41