web-dev-qa-db-ja.com

エンディアンにとらわれないC / C ++コードの書き方

私はググリングをしました、そしてこの質問についてのどんな良い記事も見つけることができませんでした。エンディアンに依存しないアプリを実装する場合、何に注意する必要がありますか?

35
d33tah

これはあなたが読むのに良い記事かもしれません: バイト順の誤り

コンピュータのバイト順は、レジスタの断片にマップされたメモリのバイトの割り当てを混乱させるコンパイラの作成者などを除いて、ほとんど問題になりません。たぶん、あなたはコンパイラの作成者ではないので、コンピュータのバイト順は少しでも重要ではありません。

「コンピュータのバイト順」というフレーズに注意してください。重要なのは、周辺機器またはエンコードされたデータストリームのバイト順ですが、これが重要な点です。処理を実行するコンピューターのバイト順は、データ自体の処理とは無関係です。データストリームがバイトオーダーBで値をエンコードする場合、バイトオーダーCでコンピューター上の値をデコードするアルゴリズムは、BとCの関係ではなく、B程度にする必要があります。

14
Pubby

エンディアンに注意する必要があるのは、エンディアンに依存しないバイナリデータ(つまり、テキストではない)を、同じエンディアンでないシステム間で転送するときだけです。通常の解決策は、「 ネットワークバイトオーダー (ビッグエンディアンとも呼ばれます)を使用してデータを転送し、必要に応じて相手側のバイトをスウィズルすることです。

ホストからネットワークバイトオーダーに変換するには、htons(3)およびhtonl(3)を使用します。元に戻すには、ntohl(3)およびntohs(3)を使用します。 man page を調べて、知っておくべきことすべてを確認してください。 64ビットデータの場合、 この質問と回答 が役立ちます。

28
Carl Norum

エンディアンに依存しないアプリを実装する場合は、何に注意する必要がありますか?

最初に、エンディアンが問題になる時期を認識する必要があります。また、ファイルからデータを読み取る場合や、コンピューター間でネットワーク通信を行う場合など、外部のどこからでもデータを読み取ったり書き込んだりする必要がある場合は、ほとんど問題になります。

このような場合、整数はメモリ内でプラットフォームごとに異なる方法で表現されるため、バイトよりも大きい整数ではエンディアンが重要になります。つまり、外部データの読み取りまたは書き込みが必要になるたびに、プログラムのメモリをダンプするだけでなく、独自の変数に直接データを読み取る必要があります。

例えばこのコードスニペットがある場合:

unsigned int var = ...;
write(fd, &var, sizeof var);

あなたはvarのメモリ内容を直接書き込んでいます。つまり、自分のコンピュータのメモリに表されているように、このデータがどこに移動してもデータが提示されます。

このデータをファイルに書き込む場合、プログラムをビッグエンディアンマシンで実行してもリトルエンディアンマシンで実行しても、ファイルの内容は異なります。そのため、そのコードはエンディアンにとらわれず、このようなことは避けたいでしょう。

代わりに、データ形式に注目してください。データの読み書きを行うときは、必ずデータ形式を決めてから、それを扱うコードを記述してください。これは、明確に定義された既存のファイル形式を読んだり、既存のネットワークプロトコルを実装したりする必要がある場合は、すでに決定されている可能性があります。

データ形式がわかったら、たとえばint変数を直接ダンプすると、コードは次のようになります。

uint32_t i = ...;
uint8_t buf[4];
buf[0] = (i&0xff000000) >> 24;
buf[1] = (i&0x00ff0000) >> 16;
buf[2] = (i&0x0000ff00) >> 8;
buf[3] = (i&0x000000ff);
write(fd, buf, sizeof buf);

これで、最上位バイトを選択してそれをバッファーの最初のバイトとして配置し、最下位バイトをバッファーの最後に配置しました。その整数は、ホストのエンディアンに関係なく、bufのビッグエンディアン形式で表されるため、このコードはエンディアンに依存しません。

このデータの利用者は、データがビッグエンディアン形式で表現されていることを知っている必要があります。そして、プログラムが実行されているホストに関係なく、このコードはそのデータを正常に読み取ります。

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[0] << 24;
i |= (uint32_t)buf[1] << 16;
i |= (uint32_t)buf[2] << 8;
i |= (uint32_t)buf[3];

逆に、読み取る必要のあるデータがリトルエンディアン形式であることがわかっている場合、エンディアネスに依存しないコードは、

uint32_t i ;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[3] << 24;
i |= (uint32_t)buf[2] << 16;
i |= (uint32_t)buf[1] << 8;
i |= (uint32_t)buf[0];

いくつかのニースインライン関数またはマクロを作成して、必要なすべての2,4,8バイト整数型をラップおよびアンラップすることができます。これらを使用して、実行するプロセッサのエンディアンではなく、データ形式に注意する場合、コードはそれが実行されているエンディアンに依存しない。

これは他の多くのソリューションよりもコードが多いため、1Gbps以上のデータをシャッフルする場合でも、この余分な作業がパフォーマンスに意味のある影響を与えるプログラムをまだ作成していません。

また、たとえば次のようなアプローチで簡単に取得できる誤ったメモリアクセスを回避できます。

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i = ntohl(*(uint32_t)buf));

また、パフォーマンスへの影響(一部の場合はわずかで、他の場合は数桁)が発生する可能性があり、整数への非整列アクセスを実行できないプラットフォームでは、さらに悪い場合にはクラッシュが発生します。

18
nos

いくつかの回答がファイルIOをカバーしていますが、これは確かに最も一般的なエンディアンの問題です。まだ言及されていないものに触れます:nions

次の共用体は、SIMD/SSEプログラミングの一般的なツールであり、notエンディアンフレンドリーです。

union uint128_t {
    _m128i      dq;
    uint64_t    dd[2];
    uint32_t    dw[4];
    uint16_t    dh[8];
    uint8_t     db[16];
};

Dd/dw/dh/db形式にアクセスするコードは、エンディアン固有の方法でアクセスします。 32ビットCPUでは、64ビット演算を32ビット部分に簡単に分割できるようにする単純な共用体を使用することも一般的です。

union u64_parts {
    uint64_t    dd;
    uint32_t    dw[2];
};

この使用例では、ユニオンの各要素を反復処理することはまれであるため、このようなユニオンを次のように記述します。

union u64_parts {
    uint64_t dd;
    struct {
#ifdef BIG_ENDIAN
        uint32_t dw2, dw1;
#else
        uint32_t dw1, dw2;
#endif
    }
};

結果は、dw1/dw2に直接アクセスするコードの暗黙的なエンディアンスワップです。上記の128ビットSIMDデータ型にも同じ設計アプローチを使用できますが、最終的にはかなり冗長になります。

免責事項:ユニオンの使用は、構造体のパディングと配置に関する緩やかな標準定義のため、しばしば不快になります。ユニオンは非常に便利で、広範囲に使用しています。非常に長い間(15歳以上)、相互互換性の問題に遭遇していません。ユニオンパディング/アラインメントは、x86、ARM、またはPowerPCをターゲットとする現在のコンパイラーで期待される一貫した方法で動作します。

8
jstine

コード内では、ほとんど無視できます-すべてがキャンセルされます。

データをディスクまたはネットワークに読み書きするときは htons を使用します

2
Martin Beckett

これは明らかにかなり物議を醸す主題です。

一般的なアプローチは、コードの入力セクションと出力セクションという1つの小さな部分のバイトオーダーのみを気にするようにアプリケーションを設計することです。

それ以外の場所では、ネイティブのバイト順を使用する必要があります。

MOSTマシンはこれを同じ方法で行いますが、浮動小数点データと整数データが​​同じ方法で格納されることは保証されていないため、物事が正しく機能することを完全に確認するには、サイズだけでなく、サイズが正しいかどうかも知る必要があります整数または浮動小数点。

もう1つの方法は、テキスト形式のデータのみを使用および生成することです。これはおそらく実装するのとほとんど同じくらい簡単です。そして、非常に少ない処理でアプリケーションにデータの出入り率が本当に高い場合を除いて、パフォーマンスの違いはほとんどありません。そして、何かに問題があるときに、出力のバイト51213498-51213501の実際の値をデコードしようとするのではなく、テキストエディタで入力データと出力データを読み取ることができるという利点があります。コード。

1
Mats Petersson

2,4または8バイトの整数型とバイトインデックス付き配列(またはその逆)の間で再解釈する必要がある場合は、エンディアンを知る必要があります。

これは、暗号化アルゴリズムの実装、シリアル化アプリケーション(ネットワークプロトコル、ファイルシステム、データベースバックエンドなど)、およびもちろんオペレーティングシステムのカーネルとドライバーで頻繁に発生します。

これは通常、ENDIANなどのマクロによって検出されます。

例えば:

uint32 x = ...;
uint8* p = (uint8*) &x;

pは、BEマシンでは上位バイト、LEマシンでは下位バイトを指しています。

あなたが書くことができるマクロを使用して:

uint32 x = ...;

#ifdef LITTLE_ENDIAN
    uint8* p = (uint8*) &x + 3;
#else // BIG_ENDIAN
    uint8* p = (uint8*) &x;
#endif

たとえば、常に上位バイトを取得します。

ここにマクロを定義する方法があります: ビッグエンディアンまたはリトルエンディアンマシンを決定するCマクロ定義? ツールチェーンがそれらを提供しない場合。

0
Andrew Tomazos