5

I'd like to output a struct's data to a binary file, but without any padding bits between each variable's information. For example:

struct s {
    int i1;
    short s1;
    char c1;
};
struct s example[2];

If I use fwrite(&example, sizeof(struct s), 2, file), the binary file still has the padding bits between, for example, s1 and c1, and also from c1 to i1 (of the 2nd struct).

What would be a good approach to remove those padding bits from the output file ?

Thanks! Any help is appreciated

  • See accepted answer to http://stackoverflow.com/questions/4306186/structure-padding-and-structure-packing – rslemos May 04 '15 at 02:40
  • 1
    This is compiler-specific. Which compiler are you using? – Greg Hewgill May 04 '15 at 02:41
  • You can iterate through the array, and write each element of the struct separately. – user12205 May 04 '15 at 02:41
  • Comprehensive material about padding and packing: [The Lost Art of C Structure Packing](http://www.catb.org/esr/structure-packing/). – rslemos May 04 '15 at 02:43
  • 1
    Well, apart from compiler-specific data packing, you could always *not* `fwrite` the structure, and instead just hit the members. Of course, its still platform-dependent, but you never mentioned platform-independence was an end-goal. If it is, padding reduction is going to be the tip of your iceberg. You'll also have potential [endianess](http://en.wikipedia.org/wiki/Endianness) issues to contend with. – WhozCraig May 04 '15 at 02:44

3 Answers3

5

I would just suggest manually reading/writing the members of the struct individually. Packing using your compiler directives can cause inefficiency and portability issues with unaligned data access. And if you have to deal with endianness, it's easy to support that later when your read operations break down to field members rather than whole structs.

Another thing, and this relates more to futuristic maintenance-type concerns, is that you don't want your serialization code or the files people have saved so far to break if you change the structure a bit (add new elements or even change the order as a cache line optimization, e.g.). So you'll potentially run into a lot less pain with code that provides a bit more breathing room than dumping the memory contents of the struct directly into a file, and it'll often end up being worth the effort to serialize your members individually.

If you want to generalize a pattern and reduce the amount of boilerplate you write, you can do something like this as a basic example to start and build upon:

struct Fields
{
     int num;
     void* ptrs[max_fields];
     int sizes[max_fields];
};

void field_push(struct Fields* fields, void* ptr, int size)
{
    assert(fields->num < max_fields);
    fields->ptrs[fields->num] = ptr;
    fields->sizes[fields->num] = size;
    ++fields->num;
}

struct Fields s_fields(struct s* inst)
{
    struct Fields new_fields;
    new_fields.num = 0;

    field_push(&new_fields, &inst->i1, sizeof inst->i1);
    field_push(&new_fields, &inst->s1, sizeof inst->s1);
    field_push(&new_fields, &inst->c1, sizeof inst->c1);

    return new_fields;
}

Now you can use this Fields structure with general-purpose functions to read and write members of any struct, like so:

void write_fields(FILE* file, struct Fields* fields)
{
    int j=0;
    for (; j < fields->num; ++j)
         fwrite(fields->ptrs[j], fields->sizes[j], 1, file);
}

This is generally a bit easier to work with than some functional for_each_field kind of approach accepting a callback.

Now all you have to worry about when you create some new struct, S, is to define a single function to output struct Fields from an instance to then enable all those general functions you wrote that work with struct Fields to now work with this new S type automatically.

2

Many compilers accept a command line parameter which means "pack structures". In addition, many accept a pragma:

#pragma pack(1)

where 1 means byte alignment, 2 means 16-bit word alignment, 4 means 32-bit word alignment, etc.

wallyk
  • 56,922
  • 16
  • 83
  • 148
1

To make your solution platform independent, you can create a function that writes each field of the struct one at a time, and then call the function to write as many of the structs as needed.

int writeStruct(struct s* obj, size_t count, FILE* file)
{
   size_t i = 0;
   for ( ; i < count; ++i )
   {
      // Make sure to add error checking code.
      fwrite(&(obj[i].i1), sizeof(obj[i].i1), 1, file);
      fwrite(&(obj[i].s1), sizeof(obj[i].s1), 1, file);
      fwrite(&(obj[i].c1), sizeof(obj[i].c1), 1, file);
   }

   // Return the number of structs written to file successfully.
   return i;
} 

Usage:

struct s example[2];
writeStruct(s, 2, file);
R Sahu
  • 204,454
  • 14
  • 159
  • 270