为什么fread会弄乱我的字节顺序?

我试图用fread()解析一个bmp文件,当我开始解析时,它会反转我的字节顺序。

 typedef struct{ short magic_number; int file_size; short reserved_bytes[2]; int data_offset; }BMPHeader; ... BMPHeader header; ... 

hex数据是42 4D 36 00 03 00 00 00 00 00 36 00 00 00 ; 我通过fread(&header,14,1,fileIn);将hex数据加载到struct中fread(&header,14,1,fileIn);

我的问题是幻数应该是0x424d //'BM' ()它将字节翻转为0x4d42 // 'MB'

为什么fread()会这样做,我该如何解决它;

编辑:如果我不够具体,我需要将整个hex数据块读入结构,而不仅仅是幻数。 我只选择了幻数作为例子。

这不是fread的错,而是你的CPU的错误,它显然是小端的。 也就是说,您的CPU将short值中的第一个字节视为 8位,而不是(正如您所预期的那样)高8位。

每当您读取二进制文件格式时,您必须显式地从文件格式的字节顺序转换为CPU的本机字节顺序。 您可以使用以下函数执行此操作:

 /* CHAR_BIT == 8 assumed */ uint16_t le16_to_cpu(const uint8_t *buf) { return ((uint16_t)buf[0]) | (((uint16_t)buf[1]) << 8); } uint16_t be16_to_cpu(const uint8_t *buf) { return ((uint16_t)buf[1]) | (((uint16_t)buf[0]) << 8); } 

您将fread放入适当大小的uint8_t缓冲区,然后手动将所有数据字节复制到BMPHeader结构,并根据需要进行转换。 这看起来像这样:

 /* note adjustments to type definition */ typedef struct BMPHeader { uint8_t magic_number[2]; uint32_t file_size; uint8_t reserved[4]; uint32_t data_offset; } BMPHeader; /* in general this is _not_ equal to sizeof(BMPHeader) */ #define BMP_WIRE_HDR_LEN (2 + 4 + 4 + 4) /* returns 0=success, -1=error */ int read_bmp_header(BMPHeader *hdr, FILE *fp) { uint8_t buf[BMP_WIRE_HDR_LEN]; if (fread(buf, 1, sizeof buf, fp) != sizeof buf) return -1; hdr->magic_number[0] = buf[0]; hdr->magic_number[1] = buf[1]; hdr->file_size = le32_to_cpu(buf+2); hdr->reserved[0] = buf[6]; hdr->reserved[1] = buf[7]; hdr->reserved[2] = buf[8]; hdr->reserved[3] = buf[9]; hdr->data_offset = le32_to_cpu(buf+10); return 0; } 

认为CPU的字节顺序与文件格式相同, 即使你知道现在它们是相同的; 无论如何你都要编写转换,以便将来你的代码可以在没有修改的情况下在具有相反字节序的CPU上工作。

通过使用固定宽度的类型,使用无符号类型,除非能够绝对需要表示负数,并且在字符数组执行时使用整数,您可以让自己更轻松。 我在上面的例子中完成了所有这些事情。 你可以看到你不需要麻烦的结尾转换幻数,因为你唯一需要做的就是测试magic_number[0]=='B' && magic_number[1]=='M'

转向相反的方向,顺便说一下,看起来像这样:

 void cpu_to_le16(uint8_t *buf, uint16_t val) { buf[0] = (val & 0x00FF); buf[1] = (val & 0xFF00) >> 8; } void cpu_to_be16(uint8_t *buf, uint16_t val) { buf[0] = (val & 0xFF00) >> 8; buf[1] = (val & 0x00FF); } 

转换32/64位数量作为练习。

我认为这是一个endian问题。 即您将字节424D放入short值。 但是你的系统是小端(我可能有错误的名字),它实际上从左到右而不是从右到左读取字节(在多字节整数类型内)。

在此代码中演示:

 #include  int main() { union { short sval; unsigned char bval[2]; } udata; udata.sval = 1; printf( "DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n" , udata.sval, udata.sval, udata.bval[0], udata.bval[1] ); udata.sval = 0x424d; printf( "DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n" , udata.sval, udata.sval, udata.bval[0], udata.bval[1] ); udata.sval = 0x4d42; printf( "DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n" , udata.sval, udata.sval, udata.bval[0], udata.bval[1] ); return 0; } 

给出以下输出

 DEC[ 1] HEX[0001] BYTES[01][00] DEC[16973] HEX[424d] BYTES[4d][42] DEC[19778] HEX[4d42] BYTES[42][4d] 

因此,如果您想要可移植,则需要检测系统的字节序,然后根据需要进行字节随机播放。 互联网上会有大量的例子来交换字节。

随后的问题:

我问的只是因为我的文件大小是3而不是196662

这是由于内存对齐问题。 196662是字节36 00 03 00 00,3是字节03 00 00 00 。 大多数系统需要类似int等类型才能拆分多个内存words 。 所以直觉上你认为你的结构在内存中如下:

  Offset short magic_number; 00 - 01 int file_size; 02 - 05 short reserved_bytes[2]; 06 - 09 int data_offset; 0A - 0D 

但是在32位系统上,这意味着files_size在与magic_number相同的word有2个字节,在下一个word有2个字节。 大多数编译器都不会支持这一点,因此结构在内存中的布局方式实际上就像:

 short magic_number; 00 - 01 <> 02 - 03 int file_size; 04 - 07 short reserved_bytes[2]; 08 - 0B int data_offset; 0C - 0F 

因此,当您在36 00读取字节流时,将进入填充区域,使得file_size保持为03 00 00 00 。 现在,如果你使用fwrite来创建这个数据,它应该没问题,因为填充字节已被写出。 但是,如果您的输入始终采用您指定的格式,那么将整个结构读取为具有fread的结构是不合适的。 相反,您需要单独阅读每个元素。

将结构体写入文件是非常不可移植的 – 最安全的是根本不尝试这样做。 使用这样的结构只能在以下情况下工作:a)结构被写入并作为结构读取(从不是字节序列)和b)它总是在相同(类型)的机器上写入和读取。 不仅有不同CPU的“endian”问题(这似乎是你遇到的问题),还存在“对齐”问题。 不同的硬件实现有关于仅在2字节甚至4字节甚至8字节边界上放置整数的不同规则。 编译器完全了解所有这些,并将隐藏的填充字节插入到结构中,因此它始终可以正常工作。 但是由于隐藏的填充字节,假设结构的字节在内存中布局就像你认为的那样是不安全的。 如果你很幸运,你在使用big-endian字节顺序的计算机上工作并且根本没有对齐限制,所以你可以直接在文件上放置结构并让它工作。 但是你可能并不那么幸运 – 当然,需要“移植”到不同机器的程序必须避免尝试直接在任何文件的任何部分放置结构。