Question

I use C bitfields to store data in memory. For archive usage these data has to be written to a file (and later on be combined with data from another machine). It seems to be a bad idea to save the bitfields directly to the file, since the arrangement of data is implementation specific.

For this reason I wrote some methods to "serialize" these bitfields to save them in a unified format:

/* uint16 is a unsigned int with size 16 */

typedef struct {
    uint16 a : 1;
    /* ... just examples ... */
    uint16 z : 13;
} data;

void save_data(FILE* fp, data d) {
    uint16 tmp;
    tmp = d.a;
    fwrite(&tmp,sizeof(uint16),1,fp);
    /* ... */
    tmp = d.z;
    fwrite(&tmp,sizeof(uint16),1,fp);
}

While this is perfectly working, it seems not to be well to extend, since adding more members in data requires adding the data to the save routine as well.

Is there any trick to automatically convert bitfield data to a unified format without needing to adapt the routine/macro when changing the bitfield data?

Was it helpful?

Solution

Here is one method. I cannot recommend it, but it's out there and it sort of works, so why not look at it. This incarnation is still platform-dependent but you can easily switch to a platform-independent, possibly human-readable format. Error handling is omitted for brevity.

// uglymacro.h
#if defined(DEFINE_STRUCT)

#define BEGINSTRUCT(struct_tag)     typedef struct struct_tag {
#define ENDSTRUCT(struct_typedef)   } struct_typedef;
#define BITFIELD(name,type,bit)     type name : bit;
#define FIELD(name,type)            type name;
#define ARRAYFIELD(name,type,size)  type name[size];

#elif defined(DEFINE_SAVE)

#define BEGINSTRUCT(struct_tag)     void save_##struct_tag(FILE* fp, \
                                                           struct struct_tag* p_a) {
#define ENDSTRUCT(struct_typedef)   }
#define BITFIELD(name,type,bit)     { type tmp; tmp = p_a->name; \
                                      fwrite (&tmp, sizeof(type), 1, fp); }
#define FIELD(name,type)            { fwrite (&p_a->name, sizeof(p_a->name), 1, fp); }
#define ARRAYFIELD(name,type,size)  { fwrite (p_a->name, sizeof(p_a->name[0]), size, fp); }

#elif defined(DEFINE_READ)

#define BEGINSTRUCT(struct_tag)     void read_##struct_tag(FILE* fp, \
                                                           struct struct_tag* p_a) {
#define ENDSTRUCT(struct_typedef)   }
#define BITFIELD(name,type,bit)     { type tmp; fread (&tmp, sizeof(type), 1, fp); \
                                      p_a->name = tmp; }
#define FIELD(name,type)            { fread (&p_a->name, sizeof(p_a->name), 1, fp); }
#define ARRAYFIELD(name,type,size)  { fread (p_a->name, sizeof(p_a->name[0]), size, fp); }

#else
#error "Must define either DEFINE_STRUCT or DEFINE_SAVE or DEFINE_READ"
#endif

#undef DEFINE_STRUCT
#undef DEFINE_READ
#undef DEFINE_WRITE
#undef BEGINSTRUCT
#undef ENDSTRUCT
#undef FIELD
#undef BITFIELD
#undef ARRAYFIELD

Your struct definition looks like this:

// mystruct_def.h
BEGINSTRUCT(mystruct)
BITFIELD(a,int,1)
FIELD(b,int)
ARRAYFIELD(c,int,10)
ENDSTRUCT(mystruct)

You use it like this:

// in mystruct.h file
#define DEFINE_STRUCT
#include "uglymacro.h"
#include "mystruct_def.h"

// in mystruct.c file
#include "mystruct.h"
#define DEFINE_READ
#include "mystruct_def.h"
#define DEFINE_WRITE
#include "mystruct_def.h"

Frankly, by modern standards this method is ugly. I have used something similar about 20 years ago and it was ugly back then.

Another alternative is using a more humane code-generation facility instead of the C preprocessor.

OTHER TIPS

If you are willing to invest a bit you can use tools like P99 for "statement unrolling":

// in header
#define MY_BITFIELDS a, z
#define WRITE_IT(X) fwrite(&(unsigned){ d.X }, sizeof(unsigned), 1, fp)    
#define WRITE_ALL(...) P99_SEP(WRITE_IT, __VA_ARGS__)


// in your function
WRITE_ALL(MY_BITFIELDS);

BTW, never use int for bitfields if you can avoid this. The semantic of a set of bits is much better matched by unsigned.

With a bit of more macro coding you could even use something like

#define MY_BITFIELDS (a, 1), (z, 11)

to produce the struct declaration and the write part.

Why not use a human readable text format?

typedef struct {
    int a : 1;
    /* ... just examples ... */
    int z : 13;
} data;

void save_data(FILE* fp, data d) {
    fprintf( fp, "a:%d\n", d.a );
    fprintf( fp, "b:%d\n", d.b );
    ...
    fprintf( fp, "z:%d\n", d.z );
}

The advantage of this technique is that somebody using any different language could quickly write a parser to load your data on any machine, any architecture.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top