将字节写入文件然后读取相同的字节是不一样的

时间:2017-06-28 19:23:37

标签: c file fopen fwrite fread

基本上我有一个文件,在这个文件中我写了3个字节,然后我写了一个4字节的整数。在另一个应用程序中,我读取前3个字节,然后读取接下来的4个字节并将它们转换为整数。

当我打印出该值时,我的结果会有很大差异......

fwrite(&recordNum, 2, 1, file);    //The first 2 bytes (recordNum is a short int)
fwrite(&charval, 1, 1, file);      //charval is a single byte char
fwrite(&time, 4, 1, file);
// I continue writing a total of 40 bytes

以下是计算时间的方法:

time_t rawtime;
struct tm * timeinfo;

time(&rawtime);
timeinfo = localtime(&rawtime);

int time = (int)rawtime;

我测试过看到sizeof(time)是4个字节,它是。我还测试了使用epoch转换器来确保这是正确的时间(以秒为单位)而且确实如此。

现在,在另一个文件中,我将40个字节读取到char缓冲区:

char record[40];
fread(record, 1, 40, file);

// Then I convert those 4 bytes into an uint32_t
uint32_t timestamp =(uint32_t)record[6] | (uint32_t)record[5] << 8 | (uint32_t)record[4] << 16 | (uint32_t)record[3] << 24;

printf("Testing timestamp = %d\n", timestamp);

但这打印出-6624。预期值为551995007。

修改

要清楚,我从char缓冲区读取的所有其他内容都是正确的。在这个时间戳之后,我有文本,我只是打印,它运行正常。

2 个答案:

答案 0 :(得分:3)

您使用fwrite一次性写入时间,它使用本机字节排序,然后以big-endian格式显式读取单个字节(最重要的字节优先)。您的机器很可能使用little-endian格式进行字节排序,这可以解释其中的差异。

您需要以一致的方式进行读/写。最简单的方法是一次fread一个变量,就像你写的那样:

fread(&recordNum, sizeof(recordNum), 1, file); 
fread(&charval, sizeof(charval), 1, file); 
fread(&time, sizeof(time), 1, file);

另请注意使用sizeof来计算尺寸。

答案 1 :(得分:2)

你的问题可能就在这里:

uint32_t timestamp =(uint32_t)record[6] | (uint32_t)record[5] << 8 | (uint32_t)record[4] << 16 | (uint32_t)record[3] << 24;
printf("Testing timestamp = %d\n", timestamp);

你已经使用fwrite来写出一个32位整数..处理器将它存储在内存中的任何顺序......你实际上并不知道机器使用了什么字节顺序(endian-ness) 。也许写出的第一个字节是整数的最低字节,或者它可能是整数的最高字节。

如果您在同一台机器上或在具有相同架构的不同机器上读取和写入数据,您不需要关心它......它会起作用。但是如果数据是在一个字节排序的架构上编写的,并且可能在具有另一个字节排序的架构上读入,那就错了:你的代码需要知道字节应该在内存中的顺序以及它们的顺序是什么在磁盘上读/写。

在这种情况下,在你的代码中,你正在混合使用它们:你用机器本身使用的任何字节顺序写出它们然后当你读入它们时,你开始转移它们就好像你知道他们原来的订单......但是你不知道,因为当你写下订单时你并没有注意订单。

因此,如果您在同一台计算机或相同的计算机(相同的处理器,操作系统,编译器等)上编写和读取文件,只需按本机顺序写出(不必担心这是什么)然后完全按照你写出来的方式阅读它们。如果你在同一台机器上编写并阅读它们,它就会起作用。

因此,如果您的时间戳位于记录的第3到第6位,请执行以下操作:

uint_32t timestamp;
memcpy(&timestamp, record+3, sizeof(timestamp);

请注意,您无法直接将record+3强制转换为uint32_t指针,因为它可能违反系统字对齐要求。

另请注意,您可能应该使用time_t类型来保存时间戳,如果您使用类似unix的系统,那么它将是提供用于保存纪元时间值的自然类型

但是,如果您计划在任何时候将此文件移动到另一台计算机并尝试在那里阅读,您可以轻松地在具有不同字节序或不同大小的time_t的系统上获得数据。简单地将字节写入文件进出文件,而不考虑不同操作系统上的字节序或类型大小,这对于临时文件或仅用于一台计算机且永远不会被移动的文件来说是很好的到其他类型的系统。

制作可在系统之间移植的数据文件本身就是一个完整的主题。但是你应该做的第一件事就是看看函数htons()ntonhs()htonl()ntonhl()和他们的同类...从系统本机端到本地端,这是一种已知的(大)端,它是互联网通信的标准,通常用于互操作性(尽管英特尔处理器现在是小端并主导市场)。这些功能类似于你在移位时的功能,但是由于其他人写了它,你不必这样做。为此使用库函数要容易得多!

例如:

#include <stdio.h>
#include <arpa/inet.h>

int main() {

    uint32_t x = 1234, y, z;

    // open a file for writing, convert x from native to big endian, write it.
    FILE *file = fopen("foo.txt", "w");
    z = htonl(x);
    fwrite(&z, sizeof(z), 1, file);
    fclose(file);

    file = fopen("foo.txt", "r");
    fread(&z, sizeof(z), 1, file);
    x = ntohl(z);
    fclose(file);        

    printf("%d\n", x);

}

注意我在这段代码中没有检查错误,它只是一个例子..不要使用像fopen,fread等函数而不检查错误。

通过在将数据写入磁盘和读取数据时使用这些函数,可以保证磁盘上的数据始终是big-endian ..例如htonl()在big-endian平台上什么都不做,当在一个小端平台上,它执行从bit到little endian的转换。而ntohl()恰恰相反。因此,您的磁盘数据将始终正确读取。