大端和小端的概念參考以前博客: 大端/小端,高字節/低字節,高地址/低地址,移位運算html
昨晚幫導師從指令中恢復圖像的時候,導師要我轉換成raw格式,也就是記錄圖像像素的二進制序列,而後反覆強調讓我注意大端小端。當時我也沒在乎,用ofstream的write方法一個個地寫進去,發現有部分數據存儲順序和其餘的不一致。因爲時間要緊,我馬上試了下FILE*而後用"wb"模式打開文件來寫,恰好要求的也是小端(由於個人win7系統就是小端存儲),結果對了當時也就沒管爲何以前C++的ofstream只有部分正確了。linux
查看了官方文檔 http://www.cplusplus.com/reference/fstream/ofstream/open/ios
open的第二個參數是打開模式的選項,其中有一個是ios_base::binary,表明二進制讀寫,我沒有加上這個選項因此纔會在寫入二進制文件時出現問題。設置這個選項在寫入二進制文件的時候就會和系統存儲順序同樣了。編程
ofstream fout; fout.open("image2.raw", ios_base::binary);
想起我以前也踩過fopen的坑,linux下不區分文本文件和二進制文件,因此"r"和"w"通用,可是windows下區分這二者,因而要用"rb"和"wb"。而C++爲了便於跨平臺,爲了這種區分文本和二進制的系統加上了binary打開方式。windows
提及來這裏我用的ios_base::binary是沒問題的,可是官網ofstream::write()函數的示例代碼用的是ofstream::binary,看了眼微軟的實現,ofstream直接繼承的ios_base的binary字段,因此二者是等價的。考慮設計者在設計的時候可能會認爲繼承自ios_base的類會作些修改吧,這裏用ofstream::binary更爲穩當。網絡
再回到大端小端的問題,若是是要跨平臺地按照小端順序寫入文件是怎樣呢?由於可能你在小端機器上寫入的,在大端機器上讀取的就會是錯誤的數值。函數
網絡編程中處理這個的手段是用htonl和htons庫函數,經過隱藏底層細節的轉換函數,把多字節整型統一按照網絡字節序(大端)來存儲。可是有時候仍然須要使用小端來存儲,因此仍是要會手寫轉換函數,這裏以小端爲例。post
好比對4字節整型:0x11223344,轉換成小端是:0x44 0x33 0x22 0x11。測試
0x44 = 0x11223344 & 0xFFui
0x33 = (0x11223344 & 0xFF00) >> 8
0x22 = (0x11223344 & 0xFF0000) >> 16
0x11 = (0x11223344 & 0xFF000000) >> 24
另外,能夠發現0xFF00即0xFF<<8,所以,能夠很天然地用一個循環實現這個邏輯。
對於2字節和8字節的整型也是相似,因此能夠用一個簡單的函數模板來實現。
#include <stdint.h> template <typename T> // T must be integer type T to_little_endian(T x) { size_t n = sizeof(T) / sizeof(uint8_t); // 2,4,8 T res; uint8_t* p = (uint8_t*) &res; for (size_t i = 0; i < n; i++) { int offset = 8 * i; p[i] = (x & (0xFF << offset)) >> offset; } return res; }
看似這個實現沒什麼問題,來寫份測試代碼看看
uint16_t i1 = 0x1122; uint32_t i2 = 0x11223344; uint64_t i3 = 0x1122334455667788; printf("%04x => %04x\n", i1, to_little_endian(i1)); printf("%08x => %08x\n", i2, to_little_endian(i2)); printf("%016lx => %016lx\n", i3, to_little_endian(i3));
結果以下,8字節整型轉換出錯
1122 => 1122 11223344 => 11223344 1122334455667788 => 1100000055667788
調試發現緣由是0xFF,這個整型字面值的默認類型是int,對於類型uint64_t(unsigned long long),int向左移位會形成溢出。因此須要顯式地把0xFF轉換爲uint64_t類型。對於這個函數模板而言,0xFF << offset表示的是x將低位所有置爲0,並捨棄高位,因此結果是不大於x的,那麼這裏0xFF只用轉換成T便可。
修改後的代碼:
template <typename T> // T must be integer type T to_little_endian(T x) { size_t n = sizeof(T) / sizeof(uint8_t); // 2,4,8 T res; uint8_t* p = (uint8_t*) &res; T mask = static_cast<T>(0xFF); for (size_t i = 0; i < n; i++) { int offset = 8 * i; p[i] = (x & (mask << offset)) >> offset; } return res; }
至於大端,只須要把int offset = 8 * i改爲int offset = 8 * (n - i - 1)便可。
OK,說實話C++的坑真是很多,類型方面的陷阱真很多。剛纔用cout打印uint8_t類型的時候也是,必須強制轉換成int,由於uint8_t和char都是1字節,因此operator<<模板會將類型識別成char,並用打印字符的方式來打印。