エンディアンの入れ替え (uint32)
エンディアンの入れ替え (uint32)
このレッスンで分かること
32bit整数0x12345678をメモリに配置するとき、12 34 56 78の順で並べるビッグエンディアンと、78 56 34 12のように逆順で並べるリトルエンディアンの二つの流派があります32bitの整数は4 byte=4 つの 8bit ブロックで構成されます- ビット演算だけで、各バイトを取り出して別の位置に置き直します
エンディアンの入れ替え とは
32bit 整数のバイト順を入れ替えてビッグエンディアンとリトルエンディアンを変換する。本レッスンでは、エンディアンの入れ替え の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
32bit 整数 0x12345678 をメモリに配置するとき、12 34 56 78 の順で並べる ビッグエンディアン と、78 56 34 12 のように逆順で並べる リトルエンディアン の二つの流派があります。CPU アーキテクチャや通信プロトコルで採用が分かれるため、バイト順 を意識せずにバイナリを読み書きすると、まったく違う値になってしまいます。
ネットワーク上では
ビッグエンディアン(network byte order) が標準。一方でx86ARMの現代 CPU はほぼリトルエンディアンです。両者の橋渡しが今回のテーマです。
バイト単位で見る 32bit
32bit の整数は 4 byte = 4 つの 8bit ブロック で構成されます。B0 B1 B2 B3 の順を B3 B2 B1 B0 に並べ替えるのが エンディアン入れ替え です。
プレーンテキスト
0x12345678
B0=0x12 B1=0x34 B2=0x56 B3=0x78
swap -> 0x78 0x56 0x34 0x12 = 0x78563412シフトとマスクで実装
ビット演算だけで、各バイトを取り出して別の位置に置き直します。
Python
def endianSwap(n):
b0 = (n >> 24) & 0xFF
b1 = (n >> 16) & 0xFF
b2 = (n >> 8) & 0xFF
b3 = n & 0xFF
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0>> 24 で最上位バイトを 右端 まで持ってきて、& 0xFF で 8bit だけ取り出します。これを 4 回繰り返し、<< 24 << 16 << 8 の位置に積み直すことで、バイト列を反転 できます。
JavaScript
function endianSwap(n) {
const b0 = (n >>> 24) & 0xff;
const b1 = (n >>> 16) & 0xff;
const b2 = (n >>> 8) & 0xff;
const b3 = n & 0xff;
return (((b3 << 24) >>> 0) | (b2 << 16) | (b1 << 8) | b0) >>> 0;
}JavaScript はビット演算で
32bit 符号付き整数として扱うため、>>> 0で符号なし化する小細工が必要です。
視覚的に理解する
32bit を超える値が来たら
本問題では 0 〜 0xFFFFFFFF を仮定します。それを超えるとマスクが効かず、上位ビットが残って思わぬ値になります。実務では n &= 0xFFFFFFFF で 下位 32bit に正規化 してから処理するのが安全です。
ネットワーク プロトコルとの関係
インターネットの世界では ネットワーク バイト オーダー = ビッグエンディアン がデファクトです。TCP IP UDP のヘッダ、DNS のメッセージ、HTTP/2 のフレームなどは全てビッグエンディアンで送受信されます。一方、x86 x86_64 の現代 PC、ARM の多くは リトルエンディアン で動いています。C 言語 の htonl (host to network long) ntohl (network to host long) というシステムコールは、まさに今回書く endianSwap と同じことをしています。Java の ByteBuffer には order(ByteOrder.LITTLE_ENDIAN) でエンディアンを切り替える API があり、内部実装はやはりシフトとマスクです。
構造体のシリアライズ
バイナリ プロトコルを設計するときは、受信側のエンディアン を仕様で固定するのが鉄則です。MessagePack Protocol Buffers などの スキーマ駆動 のフォーマットは内部でエンディアンを吸収していますが、自前で バイト列を組み立てる 場面では、書き手と読み手で約束が違うとデータが化ける 現象に必ず一度はぶつかります。
エンディアンは
バグの種類ではなく仕様の話。最初に決めて、コメントとテストで縛りましょう。
よくある間違い
<< 24ではなく<< 23などビット数を間違える- JavaScript で
>> 24(符号付き) を使い、負の値が紛れ込む - マスク
0xFFを0xFにしてしまい、4bitしか取れない
ビット演算は紙に書いて確かめる。
16 進数で書き出して、期待値と一致するかを必ず確認しましょう。
やってみよう
16bit用のswap16を書く64bit(8 byte) 用のswap64を書く (Python ならintで扱える)バイト列を表す配列[0x12, 0x34, 0x56, 0x78]を入力に取る版を書いてみる- 現在動いている環境のエンディアンを判定する関数
getEndianを書いてみる
よくある質問
Q. このトピックを覚える意味は何ですか?
A. ネットワーク通信やバイナリファイルのフォーマットを扱うとき、送信側と受信側でバイト順の解釈が食い違うと、同じビット列でもまったく違う数値として読まれてしまいます。エンディアンの概念を理解しておくと、この種の「値が化ける」バグの原因を素早く特定できます。
Q. 実務でこの知識を使う場面は?
A. ネットワークプロトコル(TCP/IPヘッダーはビッグエンディアン固定)を実装するとき、バイナリファイルフォーマットをパースするとき、異なるCPUアーキテクチャ間でシリアライズしたデータをやり取りするときに直接関わってきます。htonl/ntohlのようなOS標準関数もこの変換を行っています。
Q. 他の章とどう繋がりますか?
A. 前のレッスンで扱ったビット演算(シフト演算・マスク処理)がそのまま実装の核になります。次のバッファ範囲チェックのレッスンでも、メモリ上でデータをバイト単位で扱う視点が引き続き必要になります。
次のレッスン
次は バッファ範囲チェック に進みましょう。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- エンディアン の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. エンディアン とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- endianSwap という名前の関数を実装すること
- シフト演算とマスク 0xFF を使って各バイトを取り出すこと
- 32bit 範囲 (0 〜 0xFFFFFFFF) の入力で正しく動くこと
入出力例
test-cases.txt
endianSwap(305419896) → 2018915346
endianSwap(1) → 16777216
endianSwap(4278190080) → 255
endianSwap(0) → 0
endianSwap(2864434397) → 3721182122