2011-12-31 4 views
6

Это тестовая программа, которую я написал для более крупного проекта, над которым я работаю. Это связано с записью данных структуры на диск с помощью функции fwrite(), а затем чтения этих данных с помощью fread(). Один член структуры динамически распределяется.C 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; 
} 

И outpout

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

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

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

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

EDIT: Dan и другие правильные. Я посмотрел на выходной файл с XXD:

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

Если я распечатать адрес, «имя» содержит перед записью и после прочтения это то же самое (0xd9a030), который согласует выход из XXD.

ответ

9

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

+0

+1 Это можно проверить с помощью printfting sizeof (struct Person) и/или путем поиска внутри файла записи. fread() читает только указатель (адрес памяти), который совпадает с текущим именем 'person->. – leonbloy

+0

Единственная гарантия заключается в том, что структура имеет одинаковую длину в рамках одной и той же конфигурации среды. Это может изменить момент использования -m32, -m64, изменить параметры глобального выравнивания и т. Д. –

1

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

5

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

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

+0

+1 Спасибо, это имеет смысл после прочтения вашего ответа. – tsliwkan

Смежные вопросы