2011-12-19 49 views
11

我试图用fread()解析一个bmp文件,当我开始解析时,它颠倒了我的字节顺序。为什么fread会乱乱我的字节顺序?

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

十六进制数据是42 4D 36 00 03 00 00 00 00 00 36 00 00 00; 我被fread(&header,14,1,fileIn);

我的问题装载十六进制数据到结构就是一个神奇的数字应该是0x424d //'BM' FREAD()它翻转字节是0x4d42 // 'MB'

为什么FREAD()这样做,怎么能我修复它;

编辑:如果我不够具体,我需要读取整个块的十六进制数据到结构不只是幻数。我只是选择了魔术数字作为例子。

+8

...面包乱用你的叮咬顺序?你尝试啃食吗? – Mehrdad 2011-12-19 03:55:18

+1

你的标题不是'fread'而不是'bread'吗? – buruzaemon 2011-12-19 03:55:39

+1

对不起。我仍然需要正确使用Lions Auto。我修好了 – 2011-12-19 04:00:05

回答

14

这不是fread的错,而是你的CPU,它显然是little-endian。也就是说,您的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的字节顺序是一样的文件格式的即使你知道一个事实,现在他们是相同的;无论如何你都要编写这些转换,以便将来你的代码将在没有修改的情况下工作,并且具有相反的排序顺序。

您可以通过使用固定宽度<stdint.h>类型让生活更方便自己,通过使用除非能表示负数是绝对必需的无符号类型,并通过使用整数时,字符数组就行了。在上面的例子中,我已经完成了所有这些事情。你可以看到你不需要费力地转换幻数,因为你唯一需要做的就是测试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位值转换留给读者作为练习。

+0

如果你打算使用'uint32_t file_size',那么endianness被固定在LE,所以有理由不使用'uint16_t magic_number'。 – Gabe 2011-12-19 04:18:11

+0

不,因为*不直接将'fread'放入BMPHeader对象*中。你'fread'到'uint8_t buf [sizeof(BMPHeader)]'中,然后你手动复制每个字段,在适当的时候进行转换;因此使用两个字符的字符串作为幻数来避免转换。我也会争辩说,把“幻数”当作两个字符的字符串(在这种情况下)是更自然的。 – zwol 2011-12-19 04:27:25

+0

@Zack如何复制这种情况下的数据? – 2011-12-19 04:40:55

2

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

此代码所演示:

#include <stdio.h> 

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,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在同一word 2个字节为magic_number,并在接下来的word两个字节上。大多数编译器不会容忍这一点,所以在内存中的结构布局方式实际上是这样的:

short magic_number;  00 - 01 
<<unused padding>>  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读取整个结构是不合适的。相反,您需要分别阅读每个元素。

+0

对不起,保存得太早。现在所有 – Sodved 2011-12-19 04:23:03

+0

+ 1演示,虽然这是很好的做这里小端的假设明确。 – zwol 2011-12-19 04:36:01

+0

这只会影响“短”吗?我只问,因为我的文件大小是3而不是196662 – 2011-12-19 04:36:06

0

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

+0

感谢您分享您的知识。这是有道理的,如果我选择使它更便携,我将在未来更改代码。 – 2012-11-01 04:42:20

+0

Blender 3d将其整个文件格式基于读/写结构到文件,甚至管理指针,尾数和32/64位转换。 它的重要性不大,但我不会说 - “千万不要这么做” – ideasman42 2013-01-18 01:07:47

+0

@ ideasman42我完全不同意。恰当的读/写结构不是微不足道的,而且易于在微妙的特定于平台的方式中出错(例如不能在机器之间共享文件)。编写平台不可知的手动读取/写入字段是微不足道的,很难出错,更不用说它可以在任何地方或任何地方工作。正确地阅读/写作结构并不困难,但对于没有任何好处肯定更难。 – Kevin 2016-08-26 20:35:20