学校では教えてくれないこと

Endianってなに?

2006.9

デバイスやソフトウェアの仕様書などを見ていると、エンディアンという言葉をしばしば見かけるよね。今回の話題は、エンディアンとは何か、エンディアンによってプログラムや基板を作るときに、どう気をつけないといけないのか、について説明していこう。

バイトオーダー

すでにこの「学校では教えてくれないこと」シリーズで見てきたように、コンピュータの内部ではデータは全て2進数で扱う仕組みになっている(ただし人間が見て理解しやすいように、表記には16進数を使う)。また、2進数で 00000000 ~ 11111111 (16進数で 0x00~0xff)で表される単位、つまり8ビットを1バイトと呼び、コンピュータでデータをメモリに読み書きしたり、ディスクに読み書きするときには、1バイトを最小単位にして行うことはわかってるよね。

例えば、0x12,0x34,0x56,0x78 という4つのデータがあったとして、これをメモリ中の0x1000番地から書き込むのであれば、そのまま

0x1000番地:0x12
0x1001番地:0x34
0x1002番地:0x56
0x1003番地:0x78

と書き込めば済む。

では、これよりも大きなサイズのデータは、どうやって扱うのだろうか? 例えば、0x12345678 というデータがあったとする。これをメモリ中の0x1000番地から書き込もうとすると、1バイト単位に分割されるので、同じように

0x1000番地:0x12
0x1001番地:0x34
0x1002番地:0x56
0x1003番地:0x78

として、この連続した4番地をひとかたまりのデータとして管理してやればよさそうだね。ところが、世の中にはこのようなデータ格納方式を取らないものもあり、

0x1000番地:0x78
0x1001番地:0x56
0x1002番地:0x34
0x1003番地:0x12

と、ちょうど順番を逆転させて格納する方式があるんだ。

このバイト並びの方式をエンディアン(Endian)と呼ぶんだ。前者の、0x12(つまり桁の大きいほう)から順番に格納する方式をビッグエンディアン(Big Endian)、後者の、0x78(つまり桁の小さいほう)から順番に格納する方式をリトルエンディアン(Little Endian)と呼び、「バイトオーダーはビッグ(エンディアン形式)だね」という言い方をしたりする。

言葉の由来

ところでこのビッグエンディアンとリトルエンディアンという単語は、『ガリバー旅行記』の中のエピソードに由来するんだ。第1部「小人国」(ガリバーが浜辺で小さい人たちに、たくさんの細いロープで縛り付けられている絵本を覚えている人も多いだろう)では、卵を丸い方(大きい方)の端から割る人々 (Big Endians) と、尖った方(小さい方)の端から割る人々(Little Endians) との対立が描かれている。当時犬猿の仲だったイギリスとフランスの対立を皮肉ったものといわれているんだ。

コンピュータの世界では、桁の大きいほうから割る(格納する)人と桁の小さいほうから割る人の対立(?)というわけだ。ガリバー旅行記って強烈な風刺に満ちていて、大人になって読むとまた面白いよ。ボクはガリバー旅行記が4部作(小人国と大人国のほかに2部ある)であることも、「ラピュタ」や「ヤフー」ってガリバー旅行記が元ネタってことも大人になってから知ったんだ。

CPUとエンディアン

話をコンピュータに戻そう。

エンディアンはCPUによって決まっていて、PowerPCなどはビッグエンディアン、Intel系などはリトルエンディアン。また MIPS や ARM、SHなど、どちらにもなれるプロセッサも存在していて、バイエンディアンと呼ばれているんだ。

これらのIntel以外のCPUについては前回のこのシリーズで説明したとおりだけど、CPUによる違いはこんなところにも現れるわけだ。これらの方式、どちらのほうが優れているということはないのだけど、ハードウェアやOS、コンパイラがどちらに対応しているかによって方式は決まってくるんだ。前回説明したとおり、組込みの世界では用途に応じてさまざまなCPUが使用されるので、 エンディアンについても意識しておく必要があるんだ。

ソフトウェアで注意すべきこと

プログラムを作る場合でも、エンディアンに気をつけなければならない場合がある。エンディアンの異なる機器間でデータのやり取りを行う場合(ファイルやTCP ネットワーク通信)や、周辺デバイス(RS-232CやUSBやLAN などのコントローラデバイス)とCPUのエンディアンが異なる場合などなんだ。

先ほどの0x12345678という数字を4バイトまとめてファイルに書き込むことを考えよう。

書き込みを行う機器はビッグエンディアンだとする。ファイルには4バイトまとめて書き込まれるがデータの並びは0x12,0x34,0x56,0x78となる。

次にこのファイルを別の機器で4バイトまとめて読み出してみる。読み出し機器もビッグエンディアンであれば、元のとおり0x12345678と復元してくれるが、読み出し機器がリトルエンディアンであれば、ファイルから読み出した結果は0x78563412と解釈されてしまうんだ。

このようなことを避けるためには、

  • これを4バイトのデータとして書き込むのではなく、"12345678"という文字列に変換して書き込み、読み出し側ではこれを0x12345678という数値に再度変換する。
  • 書き込み、読み出しの双方のプログラムで、ファイルに読み書きするデータは○○○エンディアンにするという取り決めをしておき、各機器では読み書き時に正しく変換するようにしておく。

というやり方が考えられるよね。

当社のミドルウェアの場合は、コンパイル時にオプション指定することにより、ソースコードを変更することなくビッグエンディアン、リトルエンディアンどちらにでも対応できるようになっているんだ。それでは、ちょっと掘り下げて、ある2つのコンピュータがお互いに通信をすることを考えてみよう。

通信データのバイトオーダー

ある多バイトデータを通信する場合、送信側が書き込んだデータが、通信路を介して転送されて、受信側で読み出す、という流れになるね。このとき、2つのコンピュータのエンディアンが異なるとどうなるんだろうか。

送信側が何も考えずに自分のエンディアンで書いたデータを、受信側が何も考えずに自分のエンディアンでそのまま読んでしまうと、送信側が送ろうとしたデータと一致しないことになるんだ。そりゃそうだよね。

もちろんこれでは正しく通信することができない。つまり、正しくデータを受け渡すためには、「あらかじめバイトオーダーを取り決めておく」必要があるということになるんだけど、普通はあまり意識していない。それは、メジャーな通信プロトコルでは、あらかじめこれらが決められていて、みんな「当たり前のこと」として決められたエンディアンで通信しているからなんだ。

有名なところとして、ネットワーク通信で使用される「ネットワークバイトオーダー」があるんだ。TCP/IPプロトコルでは、IPアドレスやポート番号などの多バイトデータは「ビッグエンディアン」で格納すると決められているよ。USBでは逆に、基本的に「リトルエンディアン」と決められているんだ。

ただ、例外もあって、例えばUSBプリンタクラスで使用する「IEEE1284デバイスID」という2バイトデータは「ビッグエンディアン」とするよう規定されていたりする。もちろん例外はたくさんあるんだけど、大きく分けると、Intelアーキテクチャを中心とするPCの世界はリトルエンディアン、モトローラ(今は半導体部門が独立して「フリースケール」という会社になっている)やUNIXなどのキーワードでくくられる制御系の世界と、IBMに代表されるメインフレームの世界では、ビッグエンディアンでデータを処理する場合が多い。

【ビッグエンディアンとリトルエンディアンの例】
ビッグエンディアンリトルエンディアンバイエンディアン
CPU68k、PowerPC G5、SPARCx86、V850PowerPC(G5除く)、ARM、SH
プロトコルTCP/IP、VME、IEEE1394USB、PCI、ATA
コンピュータMacintosh、メインフレーム(IBM系)Windows PC

TCP/IPやUSB、IEEE1284の歴史的背景を調べてみて、「なぜこの規格はリトルエンディアンになったんだろう」とか考えてみるのも面白いね。バスももちろん通信の一種だけど、VMEバスはビッグエンディアン、PCIバスはリトルエンディアンと相性がいいんだ。

VMEはモトローラの68k、PCI はインテルのx86系プロセッサを使用するシステムを念頭において規格化されたという歴史的背景があるよ。このように、使用する通信プロトコルごとにバイトオーダーが決められていて、 この取り決めによって、いろいろな異なるアーキテクチャのコンピュータ同士が自在に相互通信できるようになっているんだね。

バイトスワップはだれがするの?

ここまで読めばわかると思うけど、通信プロトコルが決めているバイトオーダーと、コンピュータ自身のバイトオーダ(ホストバイトオーダというよ)が違う場合は、変換して処理することを忘れてはいけないよね。この変換のことをバイトスワップというんだ。

さっきは、「意識しないで使っている」と書いたけど、これは「通信プロトコルとして決まっているから、相手先と話し合ってバイトオーダーを決める必要がない」というだけのことだね。相手の機器がどのようなホストバイトオーダーを取っているとしても、TCP/IP通信なら必ずデータはビッグエンディアンでやってくるということだ。

でももちろん、こちら側の機器がIntelのx86アーキテクチャのCPUを使っていたりすれば、そのことを意識しておかなくてはならないよね。通信用ソフトウェアを作成する場合は、自分のCPUのバイトオーダーと、対象となる通信プロトコルのバイトオーダーの両方をよく知っておかなくてはならない。

たとえばTCP/IP関連を使う開発で、IntelのCPUを使っているとき、ハードウェアでバイトスワップを行う場合と、ソフトウェアで行う場合があるんだ。

ハードの方がもちろん高速に処理できるし、ソフトで意識する必要がない。でも、ソフト開発者もバイトスワップがどこで行われているのかは理解しておかなきゃいけないし、通信プロトコルやCPUのバイトオーダーはどちらなのかを常に頭においておく必要があるということだね。

いつだったか、ハード開発者が回路設計に入れてくれるはずだったバイトスワップ機能が入ってなくて、ソフトウェアで急遽バイトスワップをすることになったことがあったなあ。「パフォーマンスが遅い」とか文句を言われて、誤解を解くのが大変だったよ。

ARMやSH、PowerPCなど、 最近ではどちらのエンディアンにも対応しているバイエンディアンのCPUも増えてきているので、この開発はどちらのエンディアンで行うものであるかも最初に確認しておかないといけないね。

てっきりビッグエンディアンだと思い込んでいて、一応聞いてみたら「いや、リトルエンディアンで開発してほしい」と言われたこともあったよ。よく聞いてみると、ぼくに見えてなかった周辺機器がリトルエンディアン固定だったんだ。確認しておいてよかった~。エンディアン設定は基本的な事柄だけに、なにかと見落としがちなので、みんなも気をつけるようにね。

ページのトップへ