Don't call your functions read
and write
(these names are for Posix functions). And don't expect to be able to read again a pointer written by a different process. This is undefined behavior.
So in your write
you are (assuming a 64 bits x86 system, e.g. a Linux one) writing 12 bytes (4 i.e. sizeof(int)
+ 8 i.e. sizeof(char*)
); the last 8 bytes are the numerical value of some malloc
-ed pointer.
In your read
your are reading these 12 bytes. So you are setting the name
field to the numerical pointer which happened to be valid in the process which has done the write
. This won't work in general (e.g. because of ASLR).
In general, doing I/O on pointers is very bad. It has only sense for the same process.
What you want to do is called serialization. For software engineering reasons I recommend using textual format for serialization (e.g. JSON, perhaps using the Jansson library). Textual formats are less brittle, and easier to debug.
Assuming you would encode a student in JSON format like
{ "id":123, "name":"John Doe" }
here is a possible JSON encoding routine using Jansson:
int encode_student (FILE*fil, const Student*stu) {
json_t* js = json_pack ("{siss}",
"id", stu->id,
"name", stu->name);
int fail = json_dumpf (js, fil, JSON_INDENT(1));
if (!fail) putc('\n', fil);
json_decref (js); // will free the JSON
return fail;
}
Notice that you need a function to free a malloc
-ed Student
zone, here it is:
void destroy_student(Student*st) {
if (!st) return;
free (st->name);
free (st);
}
and you might want also the macro
#define DESTROY_CLEAR_STUDENT(st) do \
{ destroy_student(st); st = NULL; } while(0)
Now, here is the JSON decoding routine using Jansson; it gives a Student
pointer in heap (to be later destroyed by the caller with DESTROY_CLEAR_STUDENT
).
Student* decode_student(FILE* fil) {
json_error_t jerr;
memset (&jerr, 0, sizeof(jerr));
json_t *js = json_loadf(fil, JSON_DISABLE_EOF_CHECK, &err);
if (!js) {
fprintf(stderr, "failed to decode student: %s\n", err.text);
return NULL;
}
char* namestr=NULL;
int idnum=0;
if (json_unpack(js, "{siss}",
"id", &idnum,
"name", &namestr)) {
fprintf(stderr, "failed to unpack student\n");
return NULL;
};
Student* res = malloc (sizeof(Student));
if (!res) { perror("malloc student"); return NULL; };
char *name = strdup(namestr);
if (!name) { perror("strdup name"); free (res); return NULL; };
memset(res, 9, sizeof(Student));
res->id = id;
res->name = name;
json_decref(js);
return res;
}
You could also decide that you serialize in some binary format (I don't recommend doing that). Then you should define your serialization format and stick to it. Very probably you'll have to encode the student id, the length of the its name, its name....
You could also (in C99) decide that the student's name
is a flexible array member, that is declare
typedef struct _student {
int id;
char name[]; // flexible member array, conventionally \0 terminated
} Student;
You really want the student names to be of varying length. You then cannot simply put varying length records in a simple FILE
. You could use some indexed file library like GDBM (each record could be in JSON). And you probably want to use Sqlite or a real database like MariaDb or MongoDB.