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



基本上我有一个文件,在这个文件中我写了 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 = %dn", timestamp);

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


编辑

需要明确的是,我从 char 缓冲区读取的其他一切都是正确的。在此时间戳之后,我有文本,我只需打印即可运行良好。

使用fwrite一次写入时间,它使用本机字节排序,然后以大端格式显式读取各个字节(最高有效字节优先)。 您的机器可能使用小端格式进行字节排序,这可以解释差异。

您需要以一致的方式读/写。 最简单的方法是一次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 = %dn", timestamp);

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

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

在这种情况下,在你的代码中,你正在混合两者:你用机器本地使用的任何字节序写出它们......然后当你读入它们时,你开始移动这些位,就好像你知道它们最初是什么顺序一样......但你没有,因为你在写出来时没有注意顺序。

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

因此,如果您的时间戳位于记录的偏移量 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("%dn", x);

}

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

通过在将数据写出到磁盘和读回磁盘时使用这些函数,您可以保证磁盘上的数据始终是大端序的......例如,htonl()在大端序平台上什么都不做,而在小端序平台上,它会从位到小端的转换。 而ntohl()则恰恰相反。 因此,磁盘上的数据将始终被正确读取。

最新更新