エンディアンの入れ替え (uint32)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

エンディアンの入れ替え (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) が標準。一方で x86 ARM の現代 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 で最上位バイトを 右端 まで持ってきて、& 0xFF8bit だけ取り出します。これを 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符号なし化 する小細工が必要です。

視覚的に理解する

diagram (will load when visible)

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 と同じことをしています。JavaByteBuffer には order(ByteOrder.LITTLE_ENDIAN) でエンディアンを切り替える API があり、内部実装はやはりシフトとマスクです。

構造体のシリアライズ

バイナリ プロトコルを設計するときは、受信側のエンディアン を仕様で固定するのが鉄則です。MessagePack Protocol Buffers などの スキーマ駆動 のフォーマットは内部でエンディアンを吸収していますが、自前で バイト列を組み立てる 場面では、書き手と読み手で約束が違うとデータが化ける 現象に必ず一度はぶつかります。

エンディアンは バグの種類 ではなく 仕様の話。最初に決めて、コメントとテストで縛りましょう。

よくある間違い

  • << 24 ではなく << 23 などビット数を間違える
  • JavaScript で >> 24 (符号付き) を使い、負の値 が紛れ込む
  • マスク 0xFF0xF にしてしまい、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 つができることを確認しましょう。

  1. エンディアン の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. エンディアン とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. endianSwap という名前の関数を実装すること
  2. シフト演算とマスク 0xFF を使って各バイトを取り出すこと
  3. 32bit 範囲 (0 〜 0xFFFFFFFF) の入力で正しく動くこと

入出力例

test-cases.txt

endianSwap(305419896)2018915346 endianSwap(1)16777216 endianSwap(4278190080)255 endianSwap(0)0 endianSwap(2864434397)3721182122

ヒント

main.py
main.py
学習モード

メモ

エンディアンの入れ替え (uint32)

⌘S で保存