不同操作系统的结果不同

时间:2016-03-16 09:44:22

标签: c fwrite fread

[更新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位)中使用带-std = c99的gcc和

"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的大小在Ubuntu中是103204字节,在Windows中是103205字节。 似乎无法阅读数据库的整个结构。

我的问题是这个程序在不同的环境中有不同的行为?

2 个答案:

答案 0 :(得分:3)

C struct是要在内存中仅 使用的数据的表示形式。一旦您的数据离开&#34;记忆以任何方式,您需要使用marshalling(或者通常也称为序列化,这里的差异不值得讨论)。

为了做到这一点,您需要将数据转换为适合您传输到的媒体的格式,在这种情况下是磁盘。

struct不适合,主要是因为这些问题:

  • Padding。填充结构以使成员与可以更快访问它们的地址对齐,或者甚至可以访问(在某些体系结构上)。
  • Endianness。存储长于单个字节的数据的方式。这在架构之间有所不同。

我已经用C代码示例写了一个明确解决这些问题的答案see here

出于这个原因,在这种情况下使用freadfwrite的整个方法存在缺陷。在程序仍在运行时,您可以将此类数据存储方式应用于磁盘临时。一旦数据可以与不同的系统(同一系统的版本,不同的机器,操作系统,库,...)共享/交换,或者在您的程序的连续运行之间存储,您需要采用正确的编组方式。

关于您的具体情况:对fread 的调用可能返回0,因为它无法读取完整项目。 sizeof(Database)可能在这些环境之间有所不同。

虽然这只是猜测,因为您似乎没有错误检查,尤其是在打开文件时。您还可以查看errno(考虑使用strerror)提供的内容。

user3121023提出了一个非常好的观点,Peter是开放模式:在Windows上,如果文件未打开,则会对您读取或写入文件的数据进行一些转换在所谓的二进制模式中。例如,这意味着如果您的数据包含等于'\n'的字节,那么Windows will add an additional '\r'(回车)。

答案 1 :(得分:2)

首先,您需要打开二进制I / O文件(在'b'的模式字符串中包含fopen()字符)。如果没有这个,文件将以文本模式打开,除其他外,它会在系统之间以不同的方式翻译换行符。

其次,更重要的是,您需要使用sizeof()这一事实。 sizeof产生的结果是实现定义的,适用于char类型的任何内容,其定义大小为1sizeof int是实现定义的。 sizeof也会为struct类型生成不同的值,因为不同类型的成员之间可能存在填充,以满足所有struct成员的对齐要求。在C标准中定义实现的这些内容的结果是它们在实现之间(即编译器和主机系统之间)不同。

这解释了为什么您在系统之间获得不同的大小 - 每个操作在不同的系统上写入或读取不同数量的数据。

要处理这些事情,您需要使用技术来确保所有内容都相同(通常可以谨慎使用,但很难,因为某些类型的某些属性(如int和{{ 1}} padding)可以通过某些编译器上的编译选项进行更改)或者进行一些编组(将内存中的struct类型转换为某种一致的二进制格式进行输出)和解组(反向过程)。输入过程需要与输出过程相反(从文件中吸收数据作为字符数组,并解释数据以重建struct)。

即使两个系统具有相同的大小(对于所有内容),也存在基本类型(如struct)如何在内存中表示的担忧,例如:字节顺序。这也需要用输入和输出方法来处理。

一种更简单的方法是使用文本模式文件和格式化的I / O.这样做的好处是文件(很大程度上)可以传输,但也意味着更大的文件。