Разный результат в разных ОС

[Обновление 2016.03.17] Извините за это, я пропустил проверку ошибок для простоты. Я проверил ошибки, вот полный код.

#define MAX_DATA 512
#define MAX_ROWS 100
typedef struct Database {
    Address rows[MAX_ROWS];
    int num; // Number of record in the DB.
} Database;

typedef struct Address {
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
} Address;

Database *db_ptr;
FILE *file;
int return_value;
// Write to file
db_ptr = (Database*)malloc(sizeof(Database));
if(!db_ptr) {
    printf("Memory error!\n");
}
for(int i = 0; i < MAX_ROWS; i++) {
    db_ptr->rows[i].id = i;
    db_ptr->rows[i].set = 0;
}
char *filename = "test.db";
file = fopen(filename, "w");
if(!file) {
    printf("Open(w) %s fail!\n", filename);
}
return_value = fwrite(db_ptr, sizeof(Database), 1, file);
printf("The return value from fwrite = %d\n", return_value);
free(db_ptr);
fclose(file);
// Read from file
db_ptr = (Database*)malloc(sizeof(Database));
if(!db_ptr) {
    printf("Memory error!\n");
}
file = fopen(filename, "r+");
if(!file) {
    printf("Open(r+) %s fail!\n", filename);
}
return_value = fread(db_ptr, sizeof(Database), 1, file);
printf("(1)The return value from fread = %d\n", return_value);
rewind(file);
return_value = fread(db_ptr, 1, sizeof(Database), file);
printf("(2)The return value from fread = %d\n", return_value);
printf("Sizeof(Database) = %lu\n", sizeof(Database));
free(db_ptr);
fclose(file);

Результаты

"The return value from fwrite = 1"
"(1)The return value from fread = 1"
"(2)The return value from fread = 103204"
"Sizeof(Database) = 103204"

в Ubuntu15.04 (64-разрядная версия) с использованием gcc с параметром -std=c99 и

"The return value from fwrite = 1"
"(1)The return value from fread = 0"
"(2)The return value from fread = 26832"
"Sizeof(Database) = 103204"

в Windows7 (64-разрядная версия) с использованием MinGW с параметром -std=c99.

Размер test.db составляет 103204 байта в Ubuntu и 103205 байт в Windows. Кажется, не удалось прочитать всю структуру базы данных.

Мой вопрос в том, как эта программа ведет себя по-разному в разных средах?


person mingpepe    schedule 16.03.2016    source источник
comment
Вы не проверяете ошибки. Вы не печатаете errno. Вы не подтвердили наличие на диске правильного содержимого. Да ладно, базовая отладка здесь...   -  person Jonathon Reinhart    schedule 16.03.2016
comment
Где вы выделяете память?   -  person merlin2011    schedule 16.03.2016
comment
У вас есть fclose или fflush после fwrite и перед fread?   -  person kaylum    schedule 16.03.2016
comment
Предоставьте минимальный, полный и проверяемый пример.   -  person Jabberwocky    schedule 16.03.2016


Ответы (2)


C struct представляют данные, предназначенные для использования только в памяти. Как только ваши данные каким-либо образом «покидают» память, вам необходимо использовать сортировку (или часто также называется сериализацией, различия здесь обсуждать не стоит).

Для этого вам необходимо преобразовать ваши данные в формат, подходящий для носителя, на который вы их переносите, в данном случае на диск.

struct не подходит из-за следующих проблем:

  • Заполнение. Структуры дополняются, чтобы выровнять элементы по адресам, где к ним можно получить более быстрый доступ, или сделать доступ даже возможным (на некоторых архитектурах).
  • Endianness. Способ хранения данных, длина которых превышает один байт. Это зависит от архитектуры.

Я написал ответ с примером кода C, который четко решает эти проблемы, см. здесь.

По этой причине весь подход с использованием fread и fwrite в этой ситуации ошибочен. Вы можете использовать этот стиль хранения данных на диск временно, пока ваша программа все еще работает. Как только данные могут быть переданы/обменены с различными системами (версиями одной и той же системы, разными машинами, операционными системами, библиотеками и т. д.) или сохранены между последовательными запусками вашей программы, вам нужно использовать правильную сортировку.

Что касается вашего конкретного случая: вызов fread вероятно возвращает 0, поскольку он не может прочитать элемент полностью. sizeof(Database) может различаться в этих средах.

Хотя это только предположение, потому что у вас, похоже, нет проверки ошибок, особенно при открытии файлов. Также вы можете взглянуть на то, что предоставляет errno (рассмотрите возможность использования strerror).

Очень хороший момент, поднятый user3121023 и Питером, - это открытый режим: в Windows выполняются некоторые преобразования данных. вы читаете или записываете в файл, если файл не открыт в так называемом двоичном режиме. Это, например, означает, что если ваши данные содержат байт, равный '\n', то Windows добавит дополнительный '\r' (каретка возвращаться).

person Daniel Jour    schedule 16.03.2016
comment
Вся хорошая информация. Но неясно, как это объясняет, что fread возвращает 0 после fwrite. Не могли бы вы указать это в своем ответе? - person kaylum; 16.03.2016
comment
@kaylum Хороший вопрос, попытался решить эту проблему, хотя это весьма вероятно, поскольку какая-либо форма проверки ошибок, похоже, отсутствует. - person Daniel Jour; 16.03.2016

Во-первых, вам нужно открыть файл для двоичного ввода-вывода (включите символ 'b' в строку режима, заданную для fopen()). Без этого файл открывается в текстовом режиме, который, среди прочего, по-разному переводит символы, такие как перевод строки, в разных системах.

Во-вторых, и это более важно, намекает тот факт, что вам нужно использовать sizeof(). Результат, полученный с помощью sizeof, определяется реализацией для любых типов char, которые по определению имеют размер 1. sizeof int определяется реализацией. sizeof также дает разные значения для типов struct, поскольку между элементами разных типов может быть дополнение, чтобы удовлетворить требования выравнивания для всех элементов struct. Следствием того, что эти вещи определяются реализацией в стандарте C, является то, что они различаются между реализациями (то есть между компиляторами и хост-системами).

Это объясняет, почему вы получаете разные размеры между системами — разные объемы данных записываются или считываются каждой операцией в разных системах.

Чтобы справиться с этими вещами, вам либо нужно использовать методы, чтобы гарантировать, что все имеет одинаковый размер (что часто возможно с осторожностью, но сложно, потому что некоторые свойства некоторых типов (например, int и struct заполнение) могут быть изменены параметрами компиляции на некоторых компиляторах) или выполните некоторую сортировку (перевод struct типов в памяти в какой-либо согласованный двоичный формат для вывода) и распаковку (обратный процесс). Процесс ввода должен быть обратным процессу вывода (всасывать данные из файла в виде массива символов и интерпретировать данные для восстановления вашего struct).

Даже если две системы имеют одинаковые размеры (для всего), есть опасения по поводу того, как основные типы (например, int) представлены в памяти, например. порядок байтов. Это также необходимо обрабатывать с помощью метода ввода и вывода.

Один из более простых способов — использовать файлы текстового режима и форматированный ввод-вывод. Это имеет то преимущество, что файлы (в основном) переносимы, но также означает, что файл большего размера.

person Peter    schedule 16.03.2016
comment
Хорошая мысль о форматированном вводе-выводе. Это также имеет то преимущество, что сохраненные данные (в некоторой степени) доступны для чтения без использования программы. - person Daniel Jour; 16.03.2016
comment
Откройте файл для двоичного ввода-вывода! Но результаты sizeof(Database) в этом коде одинаковы. Мне все еще любопытно, в чем причина различного поведения в разных средах. - person mingpepe; 17.03.2016
comment
Посмотрите, какое значение имеют такие термины, как определенная реализация, в стандартах C. - person Peter; 17.03.2016