Различен резултат в различни ОС

[Актуализация 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 structs са представяния на данни, които са предназначени да се използват само в паметта. Веднага щом вашите данни „напуснат“ паметта по някакъв начин, трябва да използвате маршалинг (или често наричана още сериализация, разликите не си струва да се обсъждат тук).

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

struct не е подходящ поради най-вече тези проблеми:

  • Попълване. Структурите са подплатени, за да подредят членовете към адреси, където могат да бъдат достъпни по-бързо, или дори да направят достъпа възможен (при някои архитектури).
  • Endianness. Начинът, по който се съхраняват данни, които са по-дълги от един байт. Това варира между архитектурите.

Написах отговор с примерен код на C, който ясно адресира тези проблеми, вижте тук.

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

Относно вашия конкретен случай: Извикването на fread вероятно връща 0, защото не може да прочете цял елемент. sizeof(Database) може да се различава в тези среди.

Въпреки че това е само предположение, защото изглежда, че нямате въведена проверка за грешки, особено за отваряне на файловете. Също така бихте могли да погледнете какво предоставя errno (помислете дали да не използвате strerror).

Много добър момент, изтъкнат от user3121023 и Peter, е отвореният режим: В 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

Първо, трябва да отворите файла за двоичен I/O (включете символ 'b' в низа за режим, даден на fopen()). Без това файлът се отваря в текстов режим, който, наред с други неща, превежда знаци като нов ред по различен начин между системите.

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

Това обяснява защо получавате различни размери между системите - различни количества данни се записват или четат от всяка операция, на различни системи.

За да се справите с тези неща, или трябва да използвате техники, за да гарантирате, че всичко е с еднакъв размер (което често е възможно с внимание, но е трудно, защото някои от свойствата на някои типове (като int и struct padding) могат да бъдат променени от опциите за компилация на някои компилатори) или направете известно сортиране (транслирайте struct типовете в паметта в някакъв последователен двоичен формат за изход) и демаршелиране (обратния процес). Процесът на въвеждане трябва да бъде обратен на изходния процес (изсмукване на данни от файла като масив от знаци и интерпретиране на данните, за да реконструирате вашите struct).

Дори ако две системи имат еднакви размери (за всичко), има опасения как основните типове (като int) са представени в паметта, напр. endianness. Това също трябва да се обработи с метода на въвеждане и извеждане.

Един по-лесен начин е да използвате файлове в текстов режим и форматиран I/O. Това има предимството, че файловете са (до голяма степен) транспортируеми, но също така означава по-голям файл.

person Peter    schedule 16.03.2016
comment
Добра точка за форматирания IO. Това също има предимството, че съхранените данни са (до известна степен) четими без използване на програмата. - person Daniel Jour; 16.03.2016
comment
Отворете файла за двоичен I/O работи! Но резултатите от sizeof(Database) са същите в този код. Все още съм любопитен каква е причината за различното поведение в различните среди - person mingpepe; 17.03.2016
comment
Потърсете значението, което термини като дефинирана реализация имат в C стандартите. - person Peter; 17.03.2016