0

バイトとさまざまな整数データ型の間で変換できるクラスを作成しています。配列を反転してからデータを変換する代わりに、システムのエンディアンがデータと同じかどうかを判断することにしました。そうであれば、単純にデータを整数にマップします。64 ビット整数の場合は次のようになります。

result = (long)(
    (buffer[index] << 56) |
    (buffer[index + 1] << 48) |
    (buffer[index + 2] << 40) |
    (buffer[index + 3] << 32) |
    (buffer[index + 4] << 24) |
    (buffer[index + 5] << 16) |
    (buffer[index + 6] << 8) |
    (buffer[index + 7]));

システムとデータのエンディアンが異なる場合は、次のように逆になります。

result = (long)(
    (buffer[index]) |
    (buffer[index + 1] << 8) |
    (buffer[index + 2] << 16) |
    (buffer[index + 3] << 24) |
    (buffer[index + 4] << 32) |
    (buffer[index + 5] << 40) |
    (buffer[index + 6] << 48) |
    (buffer[index + 7] << 56));

resultは 64 ビットの符号付き整数です

bufferバイト配列です

index読み取りを開始するバッファ内の位置を示す 32 ビットの符号付き整数です

私の質問は...私はこれを間違っていますか、それとも配列を逆にしたりコピーを作成したりせずに変換を行うための本当に簡単な方法ですか?

これは、システムとデータのエンディアンのすべての組み合わせで機能し、2 つを正しく変換する必要があるようです。

読みやすい、または一般的により単純な別の方法はありますか?

4

4 に答える 4

1

配列を逆にする代わりに、を逆にすることができますlong(まあ、 を使うと少し簡単ですulong):

ulong raw = BitConverter.ToUInt64(array, pos);
if (wrong_endian)
{
    // swap groups of 4
    raw = (raw >> 32) | (raw << 32);
    // swap groups of 2
    raw = ((raw >> 16) & 0x0000FFFF0000FFFF) | ((raw << 16) &0xFFFF0000FFFF0000);
    // swap groups of 1
    raw = ((raw >> 8) & 0x00FF00FF00FF00FF) | ((raw << 8) & 0xFF00FF00FF00FF00);
}

コードはテストされていませんが、アイデアはわかります。配列の代わりに整数を反転します。

于 2013-04-13T09:15:43.420 に答える
0

はい、あなたはそれを正しくやっています。(コメントに記載されているバグを除く)

コードは非常に単純ですが、短くはないかもしれません。行数を減らしたい場合は、次のようにします。

result = 0;
for(var i = 0; i < 8; i++)
    result |= (long)buffer[index + i] << (8*i);

そして、コンパイラがループ展開を行うことを願っています。他のコードについても同様です:

result = 0;
for(var i = 0; i < 8; i++)
    result |= (long)buffer[index + i] << (56 - 8*i);
于 2013-04-13T09:11:52.737 に答える
0

整数とそのバイト表現の間で変換を行う場合、主に 2 つのシナリオがあります。

ネイティブ エンディアン

これは通常、ネイティブ コードと相互運用する場合に当てはまります。Buffer.BlockCopyBitConverter.ToBytes/ToInt64や安全でないコードなど、ネイティブのエンディアンを自然に使用するコードを使用します。場合によっては、p/invoke マーシャラーがほとんどの作業を行うことができます。

固定エンディアン

これは通常、ファイルまたはネットワーク プロトコルを解析する場合に当てはまります。その場合、コード片 (キャスティング バグを除く) は、それを処理する理想的な方法です。など、エンディアンを示す名前を付けますToInt64BitEndian

それらは理解しやすく、テストしやすく (システムのエンディアンに依存しない)、適度に高速です。

場合によっては、キャストを使用したり、安全でない再解釈したりすることでパフォーマンスが向上することがありますBuffer.BlockCopyが、プロファイリング後にこのコードのボトルネックを示すもののみを使用します。私のプログラムでは、これがボトルネックになったことは一度もないので、あなたの例にかなり似たコードを使用しています。

ビッグ エンディアン システムのコード パスは典型的なリトル エンディアン システムでは実行されないため、このためにベース コードをリバースするのは好きではありません。


ErrataRob のサイレント サークルのコード レビューでも同様の点が指摘されており、もう少し詳しく説明されています。

プロトコルの解析は CPU に依存しません。CPU によって何か違うことをする理由はありません。

キャストとバイトスワッピング

上記の条件分岐の誤りは、と#ifの間のキャストの根本的な誤りを修正しようとしたことに起因します。これは、「UNIX ネットワーク プログラミング」クラスで教えられる一般的な手法です。それも間違っています。パケットを解析するときは、決してそれを行うべきではありません。char*int*

これを回避する理由は 2 つあります。1 つ目は、(前述のように) SPARC や一部のバージョンの ARM などの一部の CPU が、アラインされていない整数を参照するとクラッシュすることです。これにより、RISC システム上でネットワーク コードが不安定になります。これは、通常、ほとんどの整数がとにかくアラインされているためです。安定したコードを作成する唯一の方法は、ネットワーク (またはファイル) パーサーで整数のキャストを停止することです。

2 番目の問題は、整数をキャストしないと発生しないバイト順/エンディアンとの混乱を引き起こすことです。IP アドレス「10.1.2.3」を検討してください。この数値には、値が の整数0x0a010203、または値が 0a 01 02 03 のバイト配列の 2 つの形式しかありません。問題は、リトル エンディアンのマシンがおかしいことです。整数は、x86 プロセッサの場合と0x0a010203同様に内部的に表され、バイトの順序が「スワップ」されます。03 02 01 0a

しかし、これはあなたが心配する必要のない単なる内部的な詳細です. char*ストリームを越えて aから an (またはその逆) にキャストしない限りint*、バイト順/エンディアンは問題になりません。

于 2013-04-13T09:53:07.513 に答える
0

すぐに使えるBitConverterクラスがあります。

hereから直接の例を次に示します。

byte[] bytes = { 0, 0, 0, 25 };

// If the system architecture is little-endian (that is, little end first), 
// reverse the byte array. 
if (BitConverter.IsLittleEndian)
    Array.Reverse(bytes);

int i = BitConverter.ToInt32(bytes, 0);
Console.WriteLine("int: {0}", i);
// Output: int: 25
于 2013-04-13T08:59:43.500 に答える