2007年01月07日
バイトオーダーの変換(バイトスワップ)
バイナリデータを扱うプログラムを書く上で、避けて通る事の出来ないバイトオーダーの問題。
結構頻出な問題だと思うのだが、軽くググってみただけではまとまった解説が見つからなかったので、自分用も兼ねてここにまとめて見る。
バイトオーダーとは何ぞや
バイトオーダーとは、多バイトデータをメモリ上に配置する際の方法のことで、エンディアンやエンディアンネスとも呼ばれる。
バイトオーダーはコンピュータごと(より正確にはCPUごと)に異なり、現在ではx86系で使用されるリトルエンディアン(LE)と、PowerPC/MC68000/SPARCなどで使用されるビッグエンディアン(BE)の2つに大別される。厳密にはPowerPCはG5だけがBEなCPUで、G4まではLEとBEの両方に対応したバイエンディアンCPUである。しかしながらLEで使われているPowerPCというのは聞いた事がないので、ここでは簡単化のためBE CPUとして扱う。
例えば「0x1234CDEF」という4byteのデータのメモリ配置は、LE/BEでそれぞれ以下のようになる。
メモリ番地 | LE | BE |
---|---|---|
0001 | EF | 12 |
0002 | CD | 34 |
0003 | 34 | CD |
0004 | 12 | EF |
つまり、LEの方はデータの後ろからメモリに格納して行き、BEはデータの並び順にメモリに格納して行く。人間から見れば、データが正順に格納されるBEの方が分かりやすいが、コンピュータ的にはLEの方が処理しやすいらしい。
バイトオーダーの問題
次に、上に書いたLEのメモリ番地0〜4の内容をそのままファイルに書き出したとしよう(つまりデータの保存)。ファイルの内容は「EFCD3412」となるはずだ。
今度は、そのファイルからメモリ内容を復元する事を考える(データの読み込み)。
LEの環境で作ったファイルなのだから、LE環境で読み込めば、当然元のデータである「0x1234CDEF」が得られるが、BE環境ではそうは行かない。現在のメモリ内でのデータの並びは「EF CD 34 12」であり、BEはデータを正順に格納して行く事から、この並びから得られるデータは「0xEFCD3412」となってしまう。
このように、保存元のエンディアンと読込み先のエンディアンが異なれば、保存時に意図したデータとは異なるデータになってしまう。これがバイトオーダーの問題である。
バイトオーダーの変換(バイトスワップ)
異なるエンディアン環境のデータを正しく読み込むには、データの並び順を変えてやらなければならない。これがバイトスワップである。
先の例であれば、ファイルからデータを読む込む際に、1バイト目のデータ(0xEF)を4バイト目に配置、2バイト目のデータ(0xCD)を3バイト目に配置……という処理をしてやればよい。この処理を行ってくれるC言語のマクロは、以下のようになる。尚、int型は32ビット、short型は16ビットの幅を持つものと仮定している。
#define BS_BYTE1(x) ( x & 0xFF ) #define BS_BYTE2(x) ( (x >> 8) & 0xFF ) #define BS_BYTE3(x) ( (x >> 16) & 0xFF ) #define BS_BYTE4(x) ( (x >> 24) & 0xFF ) #define BS_INT16(x) ( (unsigned short)(BS_BYTE1(x)<<8 | BS_BYTE2(x)) ) #define BS_INT32(x) ( BS_BYTE1(x)<<24 | BS_BYTE2(x)<<16 | BS_BYTE3(x)<<8 | BS_BYTE4(x) )
BS_INT16(x)が2バイトデータのバイトスワップを、BS_INT32(x)が4バイトデータのバイトスワップを行うマクロである。つまり、BS_INT32(0xEFCD3412)とすれば、0x1234CDEFというデータを得る事ができる。
動作原理を見て見よう。
まず、BS_BYTEn(x)のマクロで、右シフトと論理積を使いデータ列から1バイトずつデータを得る。そして、得たデータを左シフトし論理和を取る事でバイトオーダーの変換を行っている。具体的な動作例は以下を参考されたい。
■BS_BYTEn(x)の動作。 [1バイト目] 1110 1111 1100 1101 0011 0100 0001 0010 (元データ0xEDCD3412) AND) 0000 0000 0000 0000 0000 0000 1111 1111 (0xFFで論理積を取る) -------------------------------------------- 0000 0000 0000 0000 0000 0000 0001 0010 → 0x12 (得られるデータ) [2バイト目] 1110 1111 1100 1101 0011 0100 0001 0010 (元データ0xEDCD3412) >>8= 0000 0000 1110 1111 1100 1101 0011 0100 (8ビット右シフトする) AND) 0000 0000 0000 0000 0000 0000 1111 1111 (0xFFで論理積を取る) -------------------------------------------- 0000 0000 0000 0000 0000 0000 0011 0100 → 0x34 (得られるデータ) 同様に3,4バイト目もそれぞれ16,24ビット右シフトしてデータを得る。 ■BS_INT16(x)の動作 BS_BYTE1(x) << 8= 0000 0000 0000 0000 0001 0010 0000 0000 (0x12を8ビット左シフト) BS_BYTE2(x) OR) 0000 0000 0000 0000 0000 0000 0011 0100 (0x34と論理和を取る) --------------------------------------------------------- 0000 0000 0000 0000 0001 0010 0011 0100 → 0x1234 unsigned short = 0001 0010 0011 0100 (unsigned shortでキャストして2バイトのデータにする) BS_INT32(x)も、シフトの数と論理積の段数が増えるだけで、動作原理は同じである。
この原理を用いれば、どんな多バイトデータでもエンディアンの変換を行うことができる。変換の方向は関係なく、BEなデータを与えればLEのデータになるし、その逆もまた然りだ。
最後に、本稿を試読してくれた魔犬タソに感謝致します。