6

I have a structure:

typedef struct student {
  char *name;
  char *surname;
  int age;
} Student;

I need to write the structure into a binary file.
Here is my attempt :

Student *s = malloc(sizeof(*s));

I fill my structure with data and then I write the struct into the file with :

fwrite(s, sizeof(*s), 1, fp);

In my file, name and surname don't exist. Instead they have their respective address of their char * pointer.
How can I write a char * into a file instead of the address of the pointer ?

kenlukas
  • 3,616
  • 9
  • 25
  • 36
Sergey
  • 61
  • 1
  • 1
  • 2

7 Answers7

11

You need to dereference your pointer and write the parts of the struct. (You should not fwrite a struct directly, but rather encode its parts and write those:

Student *s = malloc(sizeof(*s));
s->name = "Jon";
s->surname = "Skeet";
s->age = 34;

// ....

fwrite(s->name, sizeof(char), strlen(s->name) + 1, fp);
fwrite(s->surname, sizeof(char), strlen(s->surname) + 1, fp);

//This one is a bit dangerous, but you get the idea.
fwrite(&(s->age), sizeof(s->age), 1, fp); 
lost_in_the_source
  • 10,998
  • 9
  • 46
  • 75
Williham Totland
  • 28,471
  • 6
  • 52
  • 68
  • but i have about 10 fields in the structure. its very long code – Sergey Gavruk May 04 '10 at 07:21
  • 1
    Then you write a function that writes all of it; and just call that; like so: `int fwriteStudent(Student *s, FILE *fp);`, and just remember to keep that function updated as you modify your struct definition. – Williham Totland May 04 '10 at 07:27
  • @Sergey: you can encapsulate these operations into a single function, so it doesn't have to be *"very long code"*. – Paul R May 04 '10 at 07:27
  • You probably mean &s->age, otherwise it's even more dangerous, and extremely unlikely to produce the deisired result. – unwind May 04 '10 at 08:27
4

You will have to do some extra work to write out the data pointed to by the structure elements - probably just write it out element by element.

Alternatively change your structure to something like this:

typedef struct
{
    char name[MAX_NAME_LEN];
    char surname[MAX_SURNAME_LEN];
    int age;
} Student;
Paul R
  • 208,748
  • 37
  • 389
  • 560
  • 2
    @Williham: fixed string lengths are fine in certain cases - for a homework problem with a small data set it's perfectly appropriate to use them. Using a packed, but more complex representation is `premature optimisation`, which is, to use your words, *"very bad"*. – Paul R May 04 '10 at 07:26
  • The [homework] tag appeared well after I gave my answer and made my comment. In any case, [homework] doesn't excuse sloppy code. – Williham Totland May 04 '10 at 07:31
  • 1
    Fixed size fields for things like human names are perfectly acceptable - after all, there is certainly a reasonable upper bound on them (is it reasonable to expect a human with a name longer than 500 characters?). As long as the code is detecting and properly dealing with attempts to store too-long input, then it is fine. (Are you aware that there's a maximum allowed length of a filename on your favourite filesystem? Can you guess what the underlying data structure looks like?) – caf May 04 '10 at 07:48
  • @caf: In a file system, *everything* is fixed size. This is because read overhead **must** be kept to an absolute minimum. This is not a file system implementation. It's a userland application. And while it surely isn't common for people to have names of length 500; it's possible, and indeed plausible for the length of names to fluctuate wildly between, say, 2 characters and, I would say, at least 50. (Three first names aren't unheard of.) That adds up to either a lot of wasted space, or occasional name overflows. And not being able to store someones full name? That is *wildly* unacceptable. – Williham Totland May 04 '10 at 07:56
  • 3
    The wasted space is a speed/space tradeoff - disk space is cheap; cheaper than CPU cycles. Not being able to store someone's full name is certainly a failure, and I would absolutely advocate a very conservative approach to that upper bound - but to argue that there *isn't* a reasonable upper bound is silly. After all, the chance of a 500 character name being entered without any errors is almost nil. Futhermore, dismal performance in a userland application can be just as much a problem as it is in a filesystem (there's a reason why people use VARCHAR2 in Oracle rather than CLOB everywhere)! – caf May 04 '10 at 08:54
  • @caf: At the point where the students is used for lookup and searched as a random access file; you need to use something else than a simple serial format, like a database (SQLite should fit the bill nicely). – Williham Totland May 04 '10 at 10:31
  • 1
    Perhaps, but then you might as well just say "don't use `fwrite`, use SQLite". If there's a need to do it using `fwrite`, then it could be that fixed-sized text fields are appropriate. Your blanket statement "Fixed string lengths are bad" is overly dogmatic, and simply wrong in many cases. It's a tradeoff, which means sometimes it will be the right answer and sometimes not. – caf May 04 '10 at 11:07
3

if you want to directly write the structure as it is to a file you would want to define the sizes of name and surname rather than dynamically allocating it.

typedef structure student { char name[100]; char surname[100]; int age; } 

else you will need to write each info in the structure one by one.

SysAdmin
  • 5,455
  • 8
  • 33
  • 34
  • 3
    @Williham Totland - I have given a reason y you would do this. i.e if you want to write the structure as it is. dont just blindly remarking this as bad. Do you have any better idea, if you want to write the structure in one fwrite operation? – SysAdmin May 04 '10 at 07:19
  • Writing the struct as one fwrite-operation is, again as has been remarked, a Bad Idea™. It is almost, but not quite, entirely non-portable, and doesn't always yield the desired results. Overflowable strings are a security breach waiting to happen. – Williham Totland May 04 '10 at 07:28
  • 3
    @Williham Totland - "it is non-portable, and does'nt always desired result" - In C this is more portable than writing each structure fields one by one. And other issues like security breach occurs just because there is no validation or just that the code you have written is plain wrong. – SysAdmin May 04 '10 at 07:36
  • 3
    Having a fixed size structure can certainly be appropriate in some cases - for example, if we are writing 15,000 of these `student` records to a file, then we could quickly seek to the 9,347th one if they are all the same size. If each one is a variable size, then we must read all 9,346 preceeding structures first (or have a separate index structure). – caf May 04 '10 at 07:45
  • @caf: If you have 15,000 student records, you probably want to be using a database, like, say, SQLite. – Williham Totland May 04 '10 at 07:47
3

In C, you have to serialize structures (convert them to a series of bytes) manually. If you want the data you output to be readable by another machine, you have to take endianness account endianness.

Here's a simple example of serializing your structure (writing it to a file), disregarding endianness/differing word size and making the code unportable:

size_t length;

length = strlen(s->name) + 1;
fwrite(&length, sizeof(length), 1, fp);
fwrite(s->name, 1, length, fp);

length = strlen(s->surname) + 1;
fwrite(&length, sizeof(length), 1, fp);
fwrite(s->surname, 1, length, fp);

fwrite(&s->age, sizeof(s->age), 1, fp);

And to unserialize:

size_t length;

fread(&length, sizeof(length), 1, fp);
s->name = malloc(length);
fread(s->name, 1, length, fp);

fread(&length, sizeof(length), 1, fp);
s->surname = malloc(length);
fread(s->surname, 1, length, fp);

fread(&s->age, sizeof(s->age), 1, fp);

In a real application, you should check the validity of your input rather than blindly assuming the input is valid and trustworthy. Also, you need to decide what byte order you'll use to store your ints, size_ts, etc, and make sure you read/write them in the correct byte order by using bit shifting.

By the way, you may want to look at tpl, a simple binary serialization library for C.

Joey Adams
  • 41,996
  • 18
  • 86
  • 115
0

You need to dereference s and individually write out each element of the structure to actually write thecontents to the file:

size_t nameCount = strlen(s->name) + 1;
fwrite(s->name, sizeof(char), nameCount, fp);

size_t surnameCount = strlen(s->surname) + 1;
fwrite(s->surname, sizeof(char), surnameCount, fp);
Raul Agrait
  • 5,938
  • 6
  • 49
  • 72
  • 1
    This won't work with the posted structure definition - `sizeof(s->name)` will give the size of a `char *`. – caf May 04 '10 at 07:14
  • so now you need to change it to strlen() * sizeof(char)... (Well, sizeof(char) will be 1, just for the principle. – rkellerm May 04 '10 at 07:20
0

Note that anyway you won't be able to see the fields in a 'normal' form because you write it to a binary file. Try to open the file without the "b" in the mode argument of fopen (r+\ w\ a etc. instead of rb+\ wb\ ab).

You can see that when using fread (from the binary file, after opening it in "rb" mode you should get the structure fields as expected.

rkellerm
  • 5,362
  • 8
  • 58
  • 95
0

You'll have to write out the fields individually, as others have said. One thing which may help reading back the data is to write out the string lengths before each entry, eg.

size_t len;
len = strlen(s->name)+1;
fwrite(&len,sizeof(len),1,stream);
fwrite(s->name,sizeof(*(s->name)),len,stream);
...

Reading back is then a matter of

Student * s = malloc(sizeof(*s));
size_t len;
fread(&len,sizeof(len),1,stream);
s->name = malloc(len);
fread(s->name,sizeof(*(s->name)),len,stream);
...
Scott Wales
  • 11,336
  • 5
  • 33
  • 30