37

グーグルで調べたところ、この質問に関する良い記事が見つかりませんでした。エンディアンに依存しないアプリを実装する場合、何に注意すればよいですか?

4

7 に答える 7

30

エンディアンを考慮する必要があるのは、エンディアンが同じではない可能性のあるシステム間で、エンディアンに依存するバイナリ データ (つまり、テキストではない) を転送する場合のみです。通常の解決策は、「ネットワーク バイト オーダー」(AKA ビッグ エンディアン) を使用してデータを転送し、必要に応じて相手側でバイトをスウィズルすることです。

ホストからネットワーク バイト オーダーに変換するには、 と を使用htons(3)htonl(3)ます。元に戻すには、 と を使用ntohl(3)ntohs(3)ます。必要なすべての情報については、man ページを参照してください。64 ビット データの場合、この質問と回答が役立ちます。

于 2012-12-21T17:38:46.033 に答える
20

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

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

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

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

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));

これは、せいぜいパフォーマンスの低下(一部では重要ではない、他では何桁も大きい)を引き起こし、整数への非整列アクセスを実行できないプラットフォームではさらに悪いことにクラッシュを引き起こす可能性があります。

于 2012-12-21T19:07:44.667 に答える
14

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

コンピューターのバイト順は、レジスター部分にマップされたメモリーのバイトの割り当てに大騒ぎするコンパイラー作成者などを除いて、まったく重要ではありません。おそらく、あなたはコンパイラ ライターではないので、コンピューターのバイト オーダーはまったく問題にならないはずです。

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

于 2012-12-21T17:38:22.750 に答える
8

ファイル IO についていくつかの回答がありましたが、これは確かに最も一般的なエンディアンの懸念事項です。まだ言及されていないものの 1 つであるUnionsについて触れます。

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

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 を対象とする現在のコンパイラに対して、期待どおりの一貫した方法で動作します。

于 2012-12-28T07:51:49.460 に答える
2

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

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

于 2012-12-21T17:39:23.763 に答える
1

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

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

それ以外の場合は、ネイティブバイトオーダーを使用する必要があります。

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

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

于 2012-12-21T19:03:52.483 に答える
0

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 ビッグ エンディアンまたはリトルエンディアン マシンを決定するマクロ定義? ツールチェーンがそれらを提供しない場合。

于 2012-12-21T17:43:40.260 に答える