将一个字节写入文件然后读取同一个字节是不一样的
Writing a byte to a file and then reading the same byte are not the same
基本上我有一个文件,在这个文件中我写了 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个字节,确实是。我还使用纪元转换器进行了测试,以确保这是正确的时间(以秒为单位)并且确实如此。
现在,在另一个文件中,我将 40 个字节读取到字符缓冲区:
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.
编辑
需要说明的是,我从字符缓冲区读取的所有其他内容都是正确的。在这个时间戳之后我有文本,我只是打印它并且运行良好。
您使用 fwrite
立即写入时间,它使用本机字节顺序,然后您以大端格式(最高有效字节在前)显式读取各个字节。您的机器可能使用小端格式进行字节排序,这可以解释差异。
您需要read/write 以一致的方式。最简单的方法是一次fread
一个变量,就像你写的那样:
fread(&recordNum, sizeof(recordNum), 1, file);
fread(&charval, sizeof(charval), 1, file);
fread(&time, sizeof(time), 1, file);
还要注意使用sizeof
来计算大小。
你的问题可能就在这里:
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 位整数。无论处理器将其存储在内存中的顺序如何。您实际上并不知道机器使用的字节顺序(字节顺序)。可能写出的第一个字节是整数的最低字节,也可能是整数的最高字节。
如果您在同一台机器上或在具有相同体系结构的不同机器上读取和写入数据,则无需关心。它会起作用。但是,如果数据是在一个字节顺序的架构上写入的,并且可能在另一个字节顺序的架构上读入,那将是错误的:您的代码需要知道字节在内存中的顺序以及它们的顺序read/written 在磁盘上。
在这种情况下,在您的代码中,您混合了两者:您以机器本机使用的任何字节序写出它们。然后当您读入它们时,您开始将位移动为如果你知道它们原来的顺序..但你不知道,因为你写的时候没有注意顺序。
因此,如果您在同一台机器或同一台机器(相同的处理器、OS、编译器等)上写入和读取文件,只需按本机顺序写出它们(不用担心那是什么),然后按照你写的那样把它们读回来。如果你在同一台机器上编写和读取它们,它就可以工作。
因此,如果您的时间戳位于记录的偏移量 3 到 6 处,只需执行以下操作:
uint_32t timestamp;
memcpy(×tamp, 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 等函数。
通过在将数据写入磁盘和读回数据时使用这些函数,您可以保证磁盘上的数据始终是大端数据。例如 htonl()
在大端平台上什么都不做,当在小端平台上时,它会进行从位到小端的转换。而 ntohl()
则相反。这样您磁盘上的数据将始终被正确读取。
基本上我有一个文件,在这个文件中我写了 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个字节,确实是。我还使用纪元转换器进行了测试,以确保这是正确的时间(以秒为单位)并且确实如此。
现在,在另一个文件中,我将 40 个字节读取到字符缓冲区:
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.
编辑
需要说明的是,我从字符缓冲区读取的所有其他内容都是正确的。在这个时间戳之后我有文本,我只是打印它并且运行良好。
您使用 fwrite
立即写入时间,它使用本机字节顺序,然后您以大端格式(最高有效字节在前)显式读取各个字节。您的机器可能使用小端格式进行字节排序,这可以解释差异。
您需要read/write 以一致的方式。最简单的方法是一次fread
一个变量,就像你写的那样:
fread(&recordNum, sizeof(recordNum), 1, file);
fread(&charval, sizeof(charval), 1, file);
fread(&time, sizeof(time), 1, file);
还要注意使用sizeof
来计算大小。
你的问题可能就在这里:
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 位整数。无论处理器将其存储在内存中的顺序如何。您实际上并不知道机器使用的字节顺序(字节顺序)。可能写出的第一个字节是整数的最低字节,也可能是整数的最高字节。
如果您在同一台机器上或在具有相同体系结构的不同机器上读取和写入数据,则无需关心。它会起作用。但是,如果数据是在一个字节顺序的架构上写入的,并且可能在另一个字节顺序的架构上读入,那将是错误的:您的代码需要知道字节在内存中的顺序以及它们的顺序read/written 在磁盘上。
在这种情况下,在您的代码中,您混合了两者:您以机器本机使用的任何字节序写出它们。然后当您读入它们时,您开始将位移动为如果你知道它们原来的顺序..但你不知道,因为你写的时候没有注意顺序。
因此,如果您在同一台机器或同一台机器(相同的处理器、OS、编译器等)上写入和读取文件,只需按本机顺序写出它们(不用担心那是什么),然后按照你写的那样把它们读回来。如果你在同一台机器上编写和读取它们,它就可以工作。
因此,如果您的时间戳位于记录的偏移量 3 到 6 处,只需执行以下操作:
uint_32t timestamp;
memcpy(×tamp, 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 等函数。
通过在将数据写入磁盘和读回数据时使用这些函数,您可以保证磁盘上的数据始终是大端数据。例如 htonl()
在大端平台上什么都不做,当在小端平台上时,它会进行从位到小端的转换。而 ntohl()
则相反。这样您磁盘上的数据将始终被正确读取。