Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C fread() magically reading dynamically allocated struct members, how?

This is a test program that I have written for a larger project that I am working on. It has to do with writing struct data to disk with fwrite() and then reading that data back with fread(). One member of the struct is dynamically allocated.

First, here is my code

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

#define STRING_LEN  128

struct Person {
    int age;
    char *name;
};

int main(int argc, const char *argv[])
{
    struct Person *person = calloc(1, sizeof(struct Person));
    person->age = 22;
    person->name = calloc(STRING_LEN, sizeof(char));

    char *name = "Name that is really, really, really, really, really, really, long.";
    strncpy(person->name, name, STRING_LEN);

    FILE *out_file = fopen("rw.out", "w");
    fwrite(person, sizeof(struct Person), 1, out_file);
    fclose(out_file);

    FILE *in_file = fopen("rw.out", "r");
    struct Person *person_read = calloc(1, sizeof(struct Person));
    fread(person_read, sizeof(struct Person), 1, in_file);
    fclose(in_file);

    printf("%d %s\n", person_read->age, person_read->name);

    free(person->name);
    free(person);
    free(person_read);

    return 0;
}

And the outpout

22 Name that is really, really, really, really, really, really, long.

My question is, why is this working? Shouldn't fwrite() only write the address that 'name' contains (i.e., the address of the beginning of the string)? That is, I am passing in sizeof(struct Person) to fwrite() and yet it is writing the string the 'name' is pointing to.

Even more confusing to me is the behavior of fread(). Again, if I am passing sizeof(struct Person), how is the actual value of 'name' being read? How is the memory for it being allocated?

My previous understanding of how to use fwrite() + fread() was that I would have to "manually" write the data that 'name' was pointing to, "manually" read that data, and then copy that string after allocating memory for both the structure and the 'name' member. In other words, I would have to traverse any pointers myself, write the data, and then read that data back in the same order.

EDIT: Dan and the others are correct. I have looked at the output file with xxd:

0000000: 1600 0000 0000 0000 30a0 d900 0000 0000  ........0.......

If I print out the address that 'name' contains before writing and after reading it is the same (0xd9a030), which matches the output from xxd.

like image 390
tsliwkan Avatar asked Dec 31 '11 02:12

tsliwkan


2 Answers

You are writing the data in the struct, which is an int followed by a pointer to a string. It's just data like anything else, and you know how long it is because the struct is fixed length - an int plus a pointer. You read the same pointer to the same name string as the original. The name itself is neither written nor read.

like image 119
Dan Avatar answered Sep 22 '22 10:09

Dan


Both person->name and person_read->name wind up pointing to the same memory location. Since you didn't deallocate person->name before reading the file back in, the pointer value in person_read->name is still valid.

If you had deallocated person->name or read the file from a different program, the pointer value would no longer be valid, and attempting to reference it would invoke undefined behavior - you would either have printed out gibberish or gotten a segfault.

like image 40
John Bode Avatar answered Sep 22 '22 10:09

John Bode