2

私は最近、BitConverter がどのように機能するかを調べており、他の SO の質問を読んだことから、開始インデックスが変換される型のサイズで割り切れる場合、ポインターをキャストできる場所に「近道」を取ることを読みました。変換先の型へのポインターへのインデックスの byte を参照し、逆参照します。

例として ToInt16 のソース:

public static unsafe short ToInt16(byte[] value, int startIndex) {
     if( value == null)  {
          ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
     }

     if ((uint) startIndex >= value.Length) {
          ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
     }

     if (startIndex > value.Length -2) {
          ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
     }
     Contract.EndContractBlock();

     fixed( byte * pbyte = &value[startIndex]) {
          if( startIndex % 2 == 0) { // data is aligned 
              return *((short *) pbyte);
          }
          else {
              if( IsLittleEndian) { 
                   return (short)((*pbyte) | (*(pbyte + 1) << 8)) ;
              }
              else {
                   return (short)((*pbyte << 8) | (*(pbyte + 1)));                        
              }
          }
     }
}

私の質問は、マシンのエンディアンに関係なくなぜこれが機能するのか、データが整列されていないときに同じメカニズムを使用しないのはなぜですか?

明確にする例:

ビッグエンディアン形式であることがわかっているバイトがいくつかbufferあり、たとえばインデックス5の配列から短い値を読み取りたいと思います。また、私のマシンはWindowsであるため、リトルエンディアンを使用していると仮定します。

バイトの順序をリトルエンディアンに切り替えることで、BitConverter を次のように使用します。

BitConverter.ToInt16(new byte[] { buffer[6], buffer[5] })

コードがショートカットを取ると仮定すると、それは私が望むことをするでしょう.提供された順序でバイトをキャストし、値を返すだけです. しかし、そのショートカット コードがなければ、バイト オーダーが再び逆になり、間違った値が返されるのではないでしょうか? または、代わりに行った場合:

BitConverter.ToInt16(new byte[] { 0, buffer[6], buffer[5] }, 1)

インデックスが 2 で割り切れないため、間違った値が得られませんか?

別の状況:

すでにリトルエンディアン形式で抽出したいが、奇数のオフセットから始まる短いバイト配列があったとします。BitConverter.IsLittleEndian が true であり、インデックスが整列されていないため、BitConverter の呼び出しでバイトの順序が逆になり、誤った値が返されることはありませんか?

4

3 に答える 3

3

このコードは、ミスアライン データ アクセスを許可しないプロセッサでのハードウェア例外 (バス エラー) を回避します。これは非常にコストがかかりますが、通常はバス アクセスを分割してバイトを結合するカーネル コードによって解決されます。このようなプロセッサは、MIPS のような RISC 設計の人気の終盤である、このコードが書かれた頃にはまだかなり一般的でした。古いARM コアと Itanium は他の例であり、それらすべてに対して .NET バージョンがリリースされています。

Intel/AMD コアなど、問題のないプロセッサではほとんど違いはありません。メモリが遅い。

このコードでは、個々のバイトにインデックスを付けているという理由だけで IsLittleEndian を使用しています。もちろん、バイトオーダーが重要になります。

于 2014-03-12T16:10:40.623 に答える
1

ほとんどのアーキテクチャでは、適切な境界に配置されていないデータにアクセスすると、パフォーマンスが低下します。x86 では、CPU はアラインされていないアドレスからの読み取りを許可しますが、パフォーマンスが低下します。一部のアーキテクチャでは、オペレーティング システムがトラップする CPU 障害が発生します。

CPU にアラインされていないデータの読み取りを修正させるコストは、個々のバイトを読み取ってシフト/操作を実行するコストよりも大きいと思います。また、このコードは、アラインされていない読み取りによってエラーが発生するプラットフォームに移植できるようになりました。

于 2014-03-12T16:04:15.427 に答える
1

マシンのエンディアンに関係なくこれが機能するのはなぜですか?

byteこのメソッドは、同じエンディアンの環境で生成されたと仮定して、 の再解釈を行います。つまり、エンディアンは、入力バイトが配列に配置される順序とshort、同じ方法で出力にバイトを配置する必要がある順序の両方に影響します。

マシンがビッグ エンディアンの場合、同じメカニズムを使用しないのはなぜですか?

これは優れた観察であり、著者がキャストを行わなかった理由はすぐにはわかりません。pbyteその背後にある理由は、奇数値を にキャストするとshort*、その後の へのアクセスshortunalignedになるためだと思います。これには、一部のプラットフォームがアラインされていないアクセスで生成するハード例外を防ぐために、特別なオペコードが必要です。

于 2014-03-12T16:05:41.077 に答える