views:

96

answers:

5

I'm trying to save a struct with a char* string into a file.

struct d_object {
    int flags;
    int time;
    int offset;
    char *filename;
};

The problem is that when doing that I will obviously only save the address of that pointer rather than the string. So what I've done is simply use a character array and but I'm forced to set the maximum size of the string. This works fine, however I was wondering if there is anyway of storing the struct with a char* (that I malloc at some point) in a file and then retrieve it. I can save the string and the struct separate and then retrieve them but it's quite a mess. It would be preferable if I could load and save the entire struct (the one above) into the file. Thanks!

The code with the char array is below:

#include <stdio.h>
#include <string.h>
#include <fcntl.h>

struct d_object {
    int flags;
    int time;
    int offset;
    char filename[255];
};

int main(int argc, char **argv) {

    struct d_object fcb;

    fcb.flags=5;
    fcb.time=100000;
    fcb.offset=220;
    strncpy(fcb.filename,"myfile",255);


    int fd=open("testfile",O_RDWR);
    write(fd,&fcb,sizeof(fcb));
    close(fd);


    int fd2 = open("testfile",O_RDONLY);
    struct d_object new_fcb; 
    read(fd2,&new_fcb,sizeof(new_fcb));

    printf("read from file testfile: %s\n",new_fcb.filename);

    return 0;

}

P.S.: I'm not using the STREAM functions simply because this is actually meant to be run on an embedded OS that doesn't have them. I've just adapted the code for *BSD/Linux so it makes more sense when asking the question.

+2  A: 

Serialization is never pretty. How about storing the length of the string in the pointer, and letting the string follow the struct in the file? Something like this (warning, brain-compiled code):

void write_object(struct d_object *s, int fd) {
    struct d_object copy = *s;
    copy.filename = (char*)strlen(s->filename);
    write(fd, &copy, sizeof(copy));
    write(fd, s->filename, (size_t)copy.filename);
}

void read_object(struct d_object *s, int fd) {
    read(fd, s, sizeof(struct d_object));
    char *filename = malloc(((size_t)s->filename) + 1);
    read(fd, filename, (size_t)s->filename);
    filename[(size_t)s->filename] = '\0';
    s->filename = filename;
}
Thomas
I don't fully agree with putting the name's length in a `char *`. That's a pragmatic solution and works, but is confusing and considered "bad style". It could even blow up if `int` ever proves larger than `char *`.
Carl Smotricz
@Carl, as much as I agree in principle, while `int` could in theory be larger than `char *`, the return value of `strlen` never could be larger than `char *` by a simple counting argument. :-)That said, rather than casting, I would use `(char *)0 + strlen(s-<filename)` and then subtract a null pointer on loading.
R..
+1  A: 

Nope, there's no magic built directly into the language to do this for you.

What you need to do is to write a couple of functions to convert your data into writable form, and to read it back in from file. This is called "serialization" / "deserialization" in languages that make more of a fuss about it.

Your particular structure, you could do something like write the binary stuff to the file straight from the struct, and then follow it up with the contents of the character buffer. You could make things easier for yourself come read time if you precede the character data with an int specifying its length.

When you read that stuff back in, you'll want to malloc/calloc yourself a chunk of memory to hold the char data in; if you stored the size you'll know just how big to make that malloc. Read the binary data into the struct, read the char data into the malloc'd memory, store the pointer to the malloc chunk into the struct, and you've re-created the original.

No magic. Just data and a bit of elbow grease.

EDIT

While I wrote about doing this, Thomas coded an example. I think our answers complement each other very well; together they should tell you everything you need to know.

Carl Smotricz
thanks, the last three comments helped with the answer
lordsandwich
+2  A: 

Your problem here is really a symptom of a much larger issue: you shouldn't be reading/writing binary data structures between memory and files. Not only is there no clear way to read/write structures with pointers to other data (this applies not only to strings but to nested data, linked lists, etc.) but the format of the data on disk will depend on your host machine and C implementation and will not be portable to other environments.

Instead, you should design a format for your data on disk and write functions to save and load the data, or use an existing format and find library code for using it. This is usually referred to as "serialization".

If your data is hierarchical, JSON, XML, or EBML may be appropriate. If it's fairly simple, flat text files or a homebrew binary format (written byte-by-byte so it's portable) may be appropriate.

Since you seem to be unfamiliar with these issues, it might be worthwhile to write some code for loading/saving a few simple binary file formats (like .tga or .wav) as an exercise before you try to design something for your own data.

R..
I know that it will not be portable and it's fine as it doesn't need to be. This code is running on an embedded OS (contiki) with 10k of ram! As you can imagine none of the technologies you mentioned will remotely work, let alone have support in this tiny os. I should have made this clear in the question. Thanks for the suggestions anyway. I'm still trying to see how to serialise the data
lordsandwich
a textfile might be the best solution I think. Or if portability is not an issue would you think it's ok to write straight binary into the file?
lordsandwich
+3  A: 

I understand that portability is not an issue, since you are working for an embedded system. In other case, you should use something like XML.

You can transform back your code to:

struct d_object {
    int flags;
    int time;
    int offset;
    char * filename;
};

And then save each piece of data individually:

write( fd, &record.flags, sizeof( int ) );
write( fd, &record.time, sizeof( int ) );
write( fd, &record.offset, sizeof( int ) );
int filename_length = strlen( filename );
write( fd, &filename_length, sizeof( int ) );
write( fd, record.filename, filename_length );

For reading, you'll have to read each item separatedly, and then the filename:

int filename_length;

read( fd, &emptyRecord.flags, sizeof( int ) );
read( fd, &emptyRecord.time, sizeof( int ) );
read( fd, &emptyRecord.offset, sizeof( int ) );

read( filename_length, sizeof( int ), 1, file );
emptyRecord.filename = (char *) malloc( sizeof( char ) * ( filename_length  +1) );
read( fd, emptyRecord.filename, filename_length );
*( emptyRecord.filename + filename_length ) = 0;
Baltasarq
thanks, the last three comments helped with the answer
lordsandwich
+1  A: 

Now that I know the nature of your problem, why not try flexible arrays? Instead of using char *filename;, use char filename[1] and malloc(sizeof struct d_object + filename_len) to allocate your structs. Add a size member, and you can easily write the object to disk with a single call to write and load it from disk with 2 calls (first to read the size element, second to read the whole object after allocating it).

Note that the "official" way to do flexible arrays in C99 is [] rather than [1], but [1] is guaranteed to work as a consequence of other requirements in the standard and works on C89 too. [1] will waste a few bytes though, so if your compiler supports [] you might want to use it.

R..