C fread () волшебным образом читает динамически размещенные элементы структуры, как? - PullRequest
7 голосов
/ 31 декабря 2011

Это тестовая программа, которую я написал для более крупного проекта, над которым я работаю.Это связано с записью данных структуры на диск с помощью fwrite () и последующим чтением этих данных с помощью fread ().Один член структуры выделяется динамически.

Во-первых, вот мой код

#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;
}

И выход

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

Мой вопрос: почему это работает?Разве fwrite () не должен писать только адрес, который содержит имя (то есть адрес начала строки)?То есть я передаю sizeof (struct Person) в fwrite (), и все же он записывает строку, на которую указывает 'name'.

Еще больше меня смущает поведение fread ().Опять же, если я передаю sizeof (struct Person), как читается фактическое значение name?Как распределяется память для него?

Мое предыдущее понимание того, как использовать fwrite () + fread (), заключалось в том, что мне пришлось бы «вручную» записывать данные, на которые указывал «имя», ««читать» эти данные вручную, а затем скопировать эту строку после выделения памяти для структуры и члена name.Другими словами, мне пришлось бы самостоятельно обходить любые указатели, записывать данные, а затем считывать эти данные обратно в том же порядке.

РЕДАКТИРОВАТЬ : Дэн и другие правы.Я посмотрел на выходной файл с xxd:

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

Если я распечатываю адрес, который содержит имя, перед записью и после прочтения он тот же (0xd9a030), который совпадает с выводом из xxd.

Ответы [ 3 ]

10 голосов
/ 31 декабря 2011

Вы записываете данные в структуру, которая представляет собой int, за которым следует указатель на строку.Это просто данные, как и все остальное, и вы знаете, как долго это происходит, потому что структура имеет фиксированную длину - int плюс указатель.Вы читаете тот же указатель на ту же строку имени, что и оригинал.Само имя не написано и не прочитано.

6 голосов
/ 31 декабря 2011

Обе person->name и person_read->name указывают на одну и ту же ячейку памяти.Так как вы не освободили person->name перед чтением файла обратно, значение указателя в person_read->name все еще допустимо.

Если вы освободили person->name или прочитали файл из другой программы, значение указателя больше не будет действительным, и попытка сослаться на него вызовет неопределенное поведение - вы либо распечатаете бред, либо получитеСегфо.

1 голос
/ 31 декабря 2011

Указатель * на имя остается действительным во время вызовов fwrite и fread, что, по-видимому, вам не повезло. Если вы освободите (person-> name) до printf, вы получите результат или ошибку, которую вы ожидали.

...