3

How can I "pack" and "write" a struct to a file using C so that:

struct a {
    uint64_t a;
    char* b;
    uint16_t c;
} a;
a b;
b.a = 3;
b.b = "Hello";
b.c = 4;

gets written to the file as

00 00 00 00 00 00 00 03 48 65 6c 6c 6f 00 00 04
hippietrail
  • 15,848
  • 18
  • 99
  • 158
Delan Azabani
  • 79,602
  • 28
  • 170
  • 210
  • Do you want the short to be stored as 8 bytes or 4? – Patrick Aug 11 '10 at 08:53
  • Can you guarantee that a long and a short are the same size? Or do you want them to be written and read as 4 byte values regardless of type? – Agnel Kurian Aug 11 '10 at 08:54
  • Patrick, "Vulcan Eager", I've made a little mistake with the output example. Fixed that, as well as changed the types of `.a` and `.c` to fixed-bit types. – Delan Azabani Aug 11 '10 at 09:18
  • My solution should still work. You need to write `append_*` functions for each type you want to support. – Agnel Kurian Aug 11 '10 at 09:28

4 Answers4

9

In C, you'll have to code a function to do this for you. You can't just blat the structure out to disk because b is a pointer that makes no sense without the backing string. And, unless you know (and can control) how your compiler packs its structures, you're better off with a utility function anyway, even without pointers.

And, as if that wasn't enough, you should output the length of the string as well so you know how many bytes to read back.

You'll be looking for something like:

int better_than_blat (FILE *f, struct a *x) {
    size_t len = strlen (x->b);
    if (fwrite (&(x->a), sizeof(long), 1, f) != 1) return -1;
    if (fwrite (&len, sizeof(size_t), 1, f) != 1) return -1;
    if (fwrite (x->b, len, 1, f) != 1) return -1;
    if (fwrite (&(x->c), sizeof(short), 1, f) != 1) return -1;
    return 0;
}

int better_than_unblat (FILE *f, struct a *x) {
    size_t len;
    if (fread (&(x->a), sizeof(long), 1, f) != 1) return -1;
    if (fread (&len, sizeof(size_t), 1, f) != 1) return -1;
    x->b = malloc (len + 1);
    if (x->b == NULL) return -1;
    memset (x->b, 0, len + 1);
    if (fread (x->b, len, 1, f) != 1) return -1;
    if (fread (&(x->c), sizeof(short), 1, f) != 1) return -1;
    return 0;
}
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • additionally, structure packaging is extremely platform/compiler dependant, even without the appearance of a char* pointer you'd need your own function to guarantee certain write outs. – KillianDS Aug 11 '10 at 09:03
2

you must write your own way to serialize this data; the compiler won't hand you a built-in way to deal with the string. There are serialization libraries out there but I don't know any for straight C.

But, consider using a more structured method for serializing data, such as json or xml. Even an INI file is better than raw binary dump. Reasons for this are:

  • easier to debug
  • more forward compatible
  • less rigid / error-prone
  • if you use an existing library, then you reap the rewards of the community that goes with it.
  • an existing library is likely to support features that you will later scratch your head over like arrays
  • better cross platform compatibility.
tenfour
  • 36,141
  • 15
  • 83
  • 142
  • it's just an example; i hate xml overuse as well, which I hoped to hint at with my INI comment. storage libs are everywhere, I'm just recommending picking one instead of writing raw bin. – tenfour Aug 11 '10 at 09:01
0

Will the following help?

struct buffer {

  char bytes[1000];
  int nbytes;
};

struct buffer *new_buffer(){
  struct buffer b = (struct buffer*) malloc(sizeof(struct buffer));
  b->nbytes = 0;
  return b;
}

void append_long(struct buffer *b, long *l){
  memcpy(b->bytes + b->nbytes, l);
  b->nbytes += sizeof(*l);
}

// ...and so on for other types

void fwrite_buffer(FILE *fp, struct buffer *b){
  fwrite(b->bytes, sizeof(*b), 1, fp);
}

Usage:

struct buffer *buf = new_buffer();
struct a b;
b.a = 3;
b.b = "Hello";
b.c = 4;
append_long(buf, &(b.a));
append_pointer(buf, &(b.b));
append_short(buf, &(b.b));
fwrite_buffer(fp, buf);
Agnel Kurian
  • 57,975
  • 43
  • 146
  • 217
0

You can safely pack your structure into byte array, if you will not use pointers in it and will explicitly define packing alignment.

For example (gcc):

struct a {
    long a;
    char b[256];
    short c;
}  __attribute__((__packed__));

int size = sizeof(a);
void* buffer = malloc(size);
memcpy(buffer, (void*)a, size);
krasnoperov
  • 353
  • 4
  • 12
  • This method is extremely fast and will give to you many problems in future. If you need simple and maintainable solution try to use ProtoBuffers or MessagePack. They are 10 times slower, but it's not a problem on practice. – krasnoperov Aug 11 '10 at 09:20