C - 二进制读取,fread正在反转顺序

时间:2015-05-19 16:27:12

标签: c binaryfiles

fread(cur,2,1,fin)

我确信当我得到答案时我会感到愚蠢,但是发生了什么?

cur是指向code_cur的指针,短(2个字节),fin是为二进制读取打开的流。

如果我的文件是00101000 01000000

我最终得到的是

code_cur = 01000000 00101000

为什么?我还没有进行任何比赛,因为问题实际上归结为(至少对我而言)意外行为。

如果这是规范,我怎样才能获得所需的效果?

P.S。

我应该补充一点,为了'查看'字节,我打印它们的整数值。

printf("%d\n",code_cur)

我尝试了几次,看起来很可靠。

3 个答案:

答案 0 :(得分:3)

正如其他人指出的那样,您需要在endianness上了解更多信息。

您不知道,但您的文件(幸运的是)在网络字节顺序(Big Endian)中。您的机器是小端,因此需要进行校正。需要与否,始终建议进行此更正,因为这将保证您的程序在任何地方运行。

做一些类似的事情:

aaItems = new ArrayAdapter(this, android.R.layout.simple_list_item_1, 
    new ArrayList<String>(arrMonths));

{ uint16_t tmp; if (1 == fread(&tmp, 2, 1, fin)) { /* Check fread finished well */ code_cur = ntohs(tmp); } else { /* Treat error however you see fit */ perror("Error reading file"); exit(EXIT_FAILURE); // requires #include <stdlib.h> } } 会将您的价值从文件订单转换为您机器的订单,无论是大端还是小端。

答案 1 :(得分:1)

这就是htonl和htons(和朋友)存在的原因。它们不是C标准库的一部分,但它们几乎可以在每个进行网络连接的平台上使用。

“htonl”的意思是“主机到网络,长”; “htons”表示“主机到网络,短”。在此上下文中,“long”表示32位,“short”表示16位(即使平台将“long”声明为64位)。基本上,每当你从“网络”(或者在你的情况下,你正在阅读的流中)读取某些内容时,你会通过“ntoh *”传递它。当你写出来时,你将它传递给“hton *”

您可以以任何您想要的方式置换这些函数名称,除了愚蠢的(不,没有ntons,也没有stonl)

答案 2 :(得分:0)

正如其他人指出的那样,这是一个流行病问题。

最高有效字节在文件和计算机上有所不同。您的文件具有高字节序(MSB在前),而计算机是低字节序(MSB在后或LSB在前)。

要了解发生了什么,让我们创建一个包含一些二进制数据的文件:

    uint8_t buffer[2] = {0x28, 0x40}; // hexadecimal for 00101000 01000000
    FILE * fp = fopen("file.bin", "wb"); // opens or creates file as binary
    fwrite(buffer, 1, 2, fp); // write two bytes to file
    fclose(fp);

file.bin已创建并保存为二进制值00101000 01000000,让我们看一下:

    uint8_t buffer[2] = {0, 0};
    FILE * fp = fopen("file.bin", "rb");
    fread(buffer, 1, 2, fp); // read two bytes from file
    fclose(fp);
    printf("0x%02x, 0x%02x\n", buffer[0], buffer[1]);
    // The above prints 0x28, 0x40, as expected and in the order we wrote previously

所以一切正常,因为我们正在逐字节读取字节,而字节没有字节序(从技术上讲,它们总是字节序,它们始终是最重要的 )机器,但您可能会认为它们并没有简化理解)。

无论如何,正如您所注意到的,当您尝试直接阅读短片时,会发生以下情况:

    FILE * fp_alt = fopen("file.bin", "rb");
    short incorrect_short = 0;
    fread(&incorrect_short, 1, 2, fp_alt);
    fclose(fp_alt);
    printf("Read short as machine endianess: %hu\n", incorrect_short);
    printf("In hex, that is 0x%04x\n", incorrect_short);
    // We get the incorrect decimal of 16424 and hex of 0x4028!
    // The machine inverted our short because of the way the endianess works internally

最糟糕的是,如果您使用的是big-endian机器,则上述结果将不会返回错误的数字,从而使您不知道您的代码是特定于endian的并且不能在处理器之间移植!

使用ntohs中的arpa/inet.h来转换字节序是很不错的,但是我觉得很奇怪,因为它是用于网络通信的完整(非标准)库,用于解决阅读时遇到的问题文件,它通过从文件中错误地读取它,然后“转换”错误的值而不是正确地读取它来解决该问题。

在高级语言中,我们经常看到用于从文件读取字节序而不是转换值的函数,因为我们(通常)知道文件结构的方式及其字节序,只需查看Javascript Buffer的readInt16BE {{3} },指向重点并易于使用。

出于这种简单性的考虑,我创建了一个函数,该函数读取下面的16位整数(但是如果需要,可以很容易地将其更改为8、32或64位):

#include <stdint.h> // necessary for specific int types

// Advances and reads a single signed 16-bit integer from the file descriptor as Big Endian
// Writes the value to 'result' pointer
// Returns 1 if succeeds or 0 if it fails
int8_t freadInt16BE(int16_t * result, FILE * f) {
    uint8_t buffer[sizeof(int16_t)];
    if (!result || !f || sizeof(int16_t) != fread((void *) buffer, 1, sizeof(int16_t), f))
        return 0;
    *result = buffer[0] << 8 + buffer[1];
    return 1;
}

用法很简单(为简便起见,省略了错误处理):

    FILE * fp = fopen("file.bin", "rb"); // Open file as binary
    short code_cur = 0;
    freadInt16BE(&code_cur, fp);
    fclose(fp);
    printf("Read Big-Endian (MSB first) short: %hu\n", code_cur);
    printf("In hex, that is 0x%04x\n", code_cur);
    // The above code prints 0x2840 correctly (decimal: 10304)

如果以下文件中的一个文件不存在,无法打开或不包含要在当前位置读取的2个字节,则该函数将失败(返回0)。

作为奖励,如果您碰巧找到的文件是little-endian,则可以使用以下功能:

// Advances and reads a single signed 16-bit integer from the file descriptor as Little Endian
// Writes the value to 'result' pointer
// Returns 1 if succeeds or 0 if it fails
int8_t freadInt16LE(int16_t * result, FILE * f) {
    uint8_t buffer[sizeof(int16_t)];
    if (!result || !f || sizeof(int16_t) != fread((void *) buffer, 1, sizeof(int16_t), f))
        return 0;
    *result = buffer[1] << 8 + buffer[0];
    return 1;
}