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

3

7

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.

Bice answered 31/12, 2011 at 2:31 Comment(1)
sizeor(char) is guaranteed to be 1Tarbox
C
10

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.

Coveney answered 31/12, 2011 at 2:35 Comment(2)
+1 You can check this by printfting sizeof(struct Person), and/or by looking inside the writen file. fread() is reading just a pointer (a memory address), which happens to be coincide with the current person->name .Cosentino
The only guarantee is that the struct is of the same length within the same environment configuration. It may change the moment you use -m32, -m64, change global alignment parameters, etc.Santamaria
W
6

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.

Workaday answered 31/12, 2011 at 2:48 Comment(1)
+1 Thanks, this makes obvious sense after reading your answer.Bice
S
1

The *name pointer remains valid throughout the fwrite and fread calls, which is seemingly a fluke to you. If you free(person->name) before printf you would get the result or error your were expecting.

Swink answered 31/12, 2011 at 2:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.