2011-12-19 50 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