どっちかわからなくなっちゃう用語の話 - ビッグエンディアン・リトルエンディアン
1. 意味 エンディアンについて詳細は「ウィキペディア - エンディアン」の方をご参照ください。 わたしは、どっちがどっちだったかすぐ忘れてしまうので、メモなのです。 SPARC がビッグエンディアンで、インテル系がリトルエンディアンでしたね。 個人的な感覚で言うなら、直感的にわかりやすいのがビッグエンディアン。ネットワークバイトオーダもビッグエンディアンです。 バイエンディアンというさらに複雑なものもあったりしますが、単純に狭義のビッグエンディアンとリトルエンディアンでいうならば、 short i = 1; を 16 進で見ると 0x0001 になるのがビッグエンディアン 0x0100 になるのがリトルエンディアンです。 プログラムを書いてみます。 #include <stdio.h> #include <errno.h> int main(int argc, char *argv[]) { union uni { char a[2]; short int b; } u = {}; u.b = 1; if (u.a[0] == 1) { printf("このマシンはリトルエンディアンです。\n"); } else { printf("このマシンはビッグエンディアンです。\n"); } return 0; } このプログラムは少なくとも i386 系、amd64 系、SPARC では正常に動作します。 2. ネットワークバイトオーダ IPv4 ネットワークでは、ポート番号や IP アドレスはビッグエンディアンで定義されています。 他の言語では知らないのですが、C言語ではこれを表現するための関数が用意されています。 #include <arpa/inet.h> or #include <netinet/in.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); n がネットワークバイトオーダで h ホストバイトオーダという意味です。 ホストの側がリトルエンディアンであれば、変換が発生し、ホストの側がビッグエンディアンであれば、変換が起こらない。 送るときに hton、受け取るときに ntoh を使用すれば、マシンがどちらのエンディアンであっても同じソースを使用できるという寸法です。 バイエンディアンというものもありまして、わたしはそれを使用したことがないのですが、きっとそれでもうまくいくはずです。 3. 異なるマシン間での通信 わたしは無知なために、ネットワーク通信を行う際、データに関しても前項の関数を使用していたのですが・・・ だがしかし・・・。これ実は厳密には間違いだったのですね。 前項の関数は、まさに IPv4 ネットワーク用の変換を行うもので、ポート番号やIPアドレスを変換するためだけに用意されているようです。 では、エンディアンの異なるマシンの間でのデータはどうしたらいいのかというと・・・。 データ用として FreeBSD では下記のものが用意されています。(FreeBSD 9.3-RELEASE の man) #include <sys/endian.h> uint16_t bswap16(uint16_t int16); uint32_t bswap32(uint32_t int32); uint64_t bswap64(uint64_t int64); uint16_t be16toh(uint16_t big16); uint32_t be32toh(uint32_t big32); uint64_t be64toh(uint64_t big64); uint16_t htobe16(uint16_t host16); uint32_t htobe32(uint32_t host32); uint64_t htobe64(uint64_t host64); uint16_t htole16(uint16_t host16); uint32_t htole32(uint32_t host32); uint64_t htole64(uint64_t host64); uint16_t le16toh(uint16_t little16); uint32_t le32toh(uint32_t little32); uint64_t le64toh(uint64_t little64); uint16_t be16dec(const void *); uint32_t be32dec(const void *); uint64_t be64dec(const void *); uint16_t le16dec(const void *); uint32_t le32dec(const void *); uint64_t le64dec(const void *); void be16enc(void *, uint16_t); void be32enc(void *, uint32_t); void be64enc(void *, uint64_t); void le16enc(void *, uint16_t); void le32enc(void *, uint32_t); void le64enc(void *, uint64_t); ところがこれらは、OS によって定義内容がいささか違っているようで、CentOS 6.4 のものを見ると #define _BSD_SOURCE #include <endian.h> uint16_t htobe16(uint16_t host_16bits); uint16_t htole16(uint16_t host_16bits); uint16_t be16toh(uint16_t big_endian_16bits); uint16_t le16toh(uint16_t little_endian_16bits); uint32_t htobe32(uint32_t host_32bits); uint32_t htole32(uint32_t host_32bits); uint32_t be32toh(uint32_t big_endian_32bits); uint32_t le32toh(uint32_t little_endian_32bits); uint64_t htobe64(uint64_t host_64bits); uint64_t htole64(uint64_t host_64bits); uint64_t be64toh(uint64_t big_endian_64bits); uint64_t le64toh(uint64_t little_endian_64bits); となっています。標準化が待たれるところです。 4. 浮動小数点数はどうするのか 前項までの話で、整数型のみしか出てきていませんが、浮動小数点数はどうするのかというと。 まず、浮動小数点数の扱いは、バイトオーダが CPU 依存するのとは別の話で、OS によって表現が大きく異なったりします。 浮動小数点数を実際にはどういう形式で表現しているかというのは難しい話になるので、ここではやめておきます。 では、どうするのかというと、浮動小数点数をデータとして送ってはいけません。 浮動小数点数のデータをネットワーク通信させるときは、以下のような方法があります。 ・文字列化する ・小数点位置を仕様で定義して整数値として送る ・分解能を仕様で定義して整数値として送る
short i = 1;
0x0001
0x0100
#include <stdio.h> #include <errno.h> int main(int argc, char *argv[]) { union uni { char a[2]; short int b; } u = {}; u.b = 1; if (u.a[0] == 1) { printf("このマシンはリトルエンディアンです。\n"); } else { printf("このマシンはビッグエンディアンです。\n"); } return 0; }
#include <arpa/inet.h> or #include <netinet/in.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
#include <sys/endian.h> uint16_t bswap16(uint16_t int16); uint32_t bswap32(uint32_t int32); uint64_t bswap64(uint64_t int64); uint16_t be16toh(uint16_t big16); uint32_t be32toh(uint32_t big32); uint64_t be64toh(uint64_t big64); uint16_t htobe16(uint16_t host16); uint32_t htobe32(uint32_t host32); uint64_t htobe64(uint64_t host64); uint16_t htole16(uint16_t host16); uint32_t htole32(uint32_t host32); uint64_t htole64(uint64_t host64); uint16_t le16toh(uint16_t little16); uint32_t le32toh(uint32_t little32); uint64_t le64toh(uint64_t little64); uint16_t be16dec(const void *); uint32_t be32dec(const void *); uint64_t be64dec(const void *); uint16_t le16dec(const void *); uint32_t le32dec(const void *); uint64_t le64dec(const void *); void be16enc(void *, uint16_t); void be32enc(void *, uint32_t); void be64enc(void *, uint64_t); void le16enc(void *, uint16_t); void le32enc(void *, uint32_t); void le64enc(void *, uint64_t);
#define _BSD_SOURCE #include <endian.h> uint16_t htobe16(uint16_t host_16bits); uint16_t htole16(uint16_t host_16bits); uint16_t be16toh(uint16_t big_endian_16bits); uint16_t le16toh(uint16_t little_endian_16bits); uint32_t htobe32(uint32_t host_32bits); uint32_t htole32(uint32_t host_32bits); uint32_t be32toh(uint32_t big_endian_32bits); uint32_t le32toh(uint32_t little_endian_32bits); uint64_t htobe64(uint64_t host_64bits); uint64_t htole64(uint64_t host_64bits); uint64_t be64toh(uint64_t big_endian_64bits); uint64_t le64toh(uint64_t little_endian_64bits);