最近の変更サイトマップ

バイトオーダーの変換(バイトスワップ)

本記事は旧blogの同名記事に加筆修正を加えた物です。

バイナリデータを扱うプログラムを書く上で、避けて通る事の出来ないバイトオーダーの問題。

結構頻出な問題だと思うのだが、軽くググってみただけではまとまった解説が見つからなかったので、自分用も兼ねてここにまとめて見る。

バイトオーダーとは何ぞや

バイトオーダーとは、多バイトデータをメモリに格納する際のバイトの配置法のことで、エンディアンやエンディアンネスとも呼ばれる。

バイトオーダーはコンピュータシステムごとに異なり、現在ではx86系などで使用されるリトルエンディアン(LE)と、PowerPC1)/MC68000/SPARCなどで使用されるビッグエンディアン(BE)の2つに大別される。スマートフォンの台頭で一般的となったARM系はLEで使われることが多い。

例えば「0x1234CDEF」という32bitの数値のメモリ配置は、LE/BEでそれぞれ以下のようになる。

番地0000000100020003
リトルEFCD3412
ビッグ1234CDEF

LEはデータの下位バイト、BEはデータの上位バイトから、それぞれメモリの下位番地より順次配置する。人間から見ればBEの方が分かりやすいが、コンピュータ的にはLEの方が都合がいいらしい。また、データの下位バイトがメモリの下位番地に格納される、という意味ではLEの方が理にかなっていると考えることもできる。

バイトオーダーの違いによる問題

ここで上表のLEのメモリ番地0000〜0003の内容をそのままファイルに書き出したとしよう(つまりはデータの保存)。ファイルの内容は「EFCD3412」となるはずだ。

次に、そのファイルからメモリの状態を復元する事を考える(データの読み込み)。

ファイル内容をそっくりメモリに展開すると、番地0000から上位番地へ向かって EF CD 34 12 の順で書き込まれる。このメモリ内容は、LE環境ならば元のデータ0x1234CDEFとなるが、BE環境だと0xEFCD3412と解釈され保存した値とは違う値に化けてしまう。

このように、データ保存環境のエンディアンとデータ読込環境のエンディアンが異なれば、保存時に意図したデータとは異なるデータになってしまう。これでは宜しくない。

バイトスワップ

異なるエンディアンで保存されたデータを正しく読み込むには、データの並び順を変えてやらなければならない。これがバイトスワップである。

先の例であれば、データをメモリに展開する際に

  • 1バイト目のデータ(0xEF)を4バイト目に配置
  • 2バイト目のデータ(0xCD)を3バイト目に配置
  • 3バイト目のデータ(0x34)を2バイト目に配置
  • 4バイト目のデータ(0x12)を1バイト目に配置

という処理を施せばよい。

通常はメモリ上のデータのエンディアンを入れ替える

この処理を行うC言語のマクロは、以下のようになる。一応、1バイト = 8ビットが前提の実装となっているが、それ以外の環境に触れている方はこのページを読まないと思うので気にしない事にする。

#define _BYTE1(x) (  x        & 0xFF )
#define _BYTE2(x) ( (x >>  8) & 0xFF )
#define _BYTE3(x) ( (x >> 16) & 0xFF )
#define _BYTE4(x) ( (x >> 24) & 0xFF )
 
#define BYTE_SWAP_16(x) ((uint16_t)( _BYTE1(x)<<8 | _BYTE2(x) ))
#define BYTE_SWAP_32(x) ((uint32_t)( _BYTE1(x)<<24 | _BYTE2(x)<<16 | _BYTE3(x)<<8 | _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のデータになるし、その逆もまた然りだ。

1) ビッグエンディアンなのはG5のみ。G4まではバイエンディアン。
programming/バイトオーダーの変換_バイトスワップ.txt · 最終更新: 2016-04-06 10:45 by decomo
CC Attribution-Noncommercial-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0