ビットシフトはエンディアンに依存しますか?


156

私が'numb'=1025 [00000000 00000000 00000100 00000001]表されている数を持っていると仮定します:

リトルエンディアンマシンの場合:

00000001 00000100 00000000 00000000

ビッグエンディアンマシンの場合:

00000000 00000000 00000100 00000001

ここで、左シフトを10ビットに適用すると(つまり、numb << = 10)、次のようになります。

[A]リトルエンディアンマシンの場合:

GDBで気付いたように、リトルエンディアンは左シフトを3ステップで実行します。[処理のみを理解するために「3」ステップを示しました]

  1. いいえを扱います。ビッグエンディアン条約:

    00000000        00000000        00000100    00000001
  2. 左シフトを適用:

    00000000        00010000        00000100        00000000
  3. 結果をリトルエンディアンで再度表します。

    00000000        00000100        00010000        00000000 

[B]。ビッグエンディアンマシンの場合:

00000000        00010000        00000100        00000000

私の質問は:

リトルエンディアンコンベンションに直接左シフトを適用すると、次のようになります。

numb

00000001 00000100 00000000 00000000

numb << 10

00010000 00000000 00000000 00000000

しかし、実際には次のようになります。

00000000        00000100        00010000        00000000 

2番目の結果のみを達成するために、上記の3つの仮想ステップを示しました。

上記の2つの結果が異なる理由を教えてください。実際の結果はnumb << 10期待される結果とは異なります。

回答:


194

エンディアンは、値がメモリに格納される方法です。プロセッサにロードされると、エンディアンに関係なく、ビットシフト命令はプロセッサのレジスタの値で動作します。したがって、メモリからプロセッサへのロードは、ビッグエンディアンに変換することと同じです。次にシフト操作が行われ、新しい値がメモリに戻されて、リトルエンディアンのバイトオーダーが再び有効になります。

更新、@ jwwに感謝:PowerPCでは、ベクトルのシフトと回転はエンディアンに依存します。ベクトルレジスタに値を設定すると、シフトによって、リトルエンディアンとビッグエンディアンで異なる結果が生成されます。


4
説明ありがとう。私がそのような複雑さをよりよく理解できる参考文献を提案していただけませんか。
Sandeep Singh

4
エンディアンを理解するための最善の方法は、エンディアンを組み込みレベルのさまざまなアーキテクチャで実際に使用することです。:しかし、私はこの二つの記事にあなたを参照することができcodeproject.com/KB/cpp/endianness.aspxibm.com/developerworks/aix/library/au-endianc/...
カール・

3
だから私のコードはエンディアンに関係なく動作しますか?!これは素晴らしい!私は自分のコードをハックして地獄に戻らなければならないと心配していました!
MarcusJ 2016年

2
@MarcusJ:必ずしもそうではありません。たとえば、32ビット整数を表すファイルから4バイトを読み取る場合、適切に解釈するには、データを受信するシステムのエンディアンと併せて、読み取るデータのエンディアンを考慮する必要があります。データ。
Carl

3
PowerPCでは、ベクトルのシフトと回転はエンディアンに依存します。ベクトルレジスタに値を設定すると、シフトによってリトルエンディアンとビッグエンディアンで異なる結果が生成されます。
jww 2018年

58

いいえ、ビットシフトは、Cの他の部分と同様に、表現ではなくに関して定義されます。1の左シフトは2の乗算、右シフトは除算です。(ビットごとの演算を使用するときはいつものように、符号付きに注意してください。符号なし整数型の場合、すべてが最も明確に定義されています。)


1
これは基本的に整数演算に当てはまりますが、Cは表現に依存する動作の多くのケースを提供します。
エドモンド

2
@Edmund:うーん...最も重要なのは、符号付きの実装が指定されていないため、ビット単位の演算(右シフトなど)とモジュロおよび除算の動作は、負の整数で定義された実装です。実装で定義される他にどのようなことを心に留めていますか
Kerrek SB、2011

@KerrekSBは残念ながら負の整数で定義された実装ではありません。それらはC89では規定されておらず、C99 +では規定されていません。
Paolo Bonzini、2016年

@PaoloBonzini:はい、良い点です。実際には、シフト操作が値で定義され、結果が表現できない場合は未定義になる可能性があり、基になる表現について推測することは役に立たないという点を補強するので、それはさらに優れています。
Kerrek SB 2016

@KerrekSB:事柄によっては、実際には誰もが左シフトを値と表現の両方で表す必要があるということです。また、符号なし整数を使用すると、他の問題が発生するx &= -1u << 20可能性があります。たとえば、x64ビットとint32 ビットの場合は正しくない可能性があります。このため、GCCは、符号付きシフトを未定義または未指定として処理しないことを約束します。
Paolo Bonzini、2016年

5

どちらのシフト命令でも、上位ビットを最初にシフトアウトすると、左シフトと見なされます。最初に下位ビットをシフトアウトするシフト命令は、どちらも右シフトと見なされます。その意味では、数値の動作は>>、エンディアンに依存しません。<<unsigned


4

コンピューターは、私たちのように数字を書き留めません。値は単純にシフトします。それをバイト単位で確認することを主張する場合(コンピューターではそうではありませんが)、リトルエンディアンマシンでは、最初のバイトが左にシフトし、余分なビットは2番目のバイトに入ると言えます。等々。

(ちなみに、バイトを水平方向ではなく垂直方向に書き込み、上位のアドレスを上に置くと、リトルエンディアンの方が理にかなっています。これがメモリマップ図の一般的な描画方法です。)


2

受け入れられた答えは、エンディアンが記憶の観点からの概念であることを指摘していますが。しかし、私はそれが質問に直接答えるとは思いません。

一部の回答では、ビット単位の演算はエンディアン依存せず、プロセッサは他の方法でバイトを表す場合があります。とにかく、それはエンディアンが抽象化されることについて話している。

しかし、たとえば紙でビットごとの計算をするとき、最初にエンディアンを述べる必要はありませんか?ほとんどの場合、エンディアンを暗黙的に選択します。

たとえば、次のようなコード行があるとします。

0x1F & 0xEF

紙の上でどのように結果を手作業で計算しますか?

  MSB   0001 1111  LSB
        1110 1111
result: 0000 1111

そこで、ここではビッグエンディアン形式を使用して計算を行います。リトルエンディアンを使用して、同じ結果を計算して取得することもできます。

ところで、コードで数値を書くと、ビッグエンディアン形式のようなものだと思います。123456または0x1F、最も重要な数字は左から始まります。

繰り返しますが、紙に値のバイナリ形式を書き込むとすぐに、エンディアネスをすでに選択していると思います。メモリから見ると、値が表示されています。

したがって、質問に戻ると、シフト操作<<LSB(最下位バイト)からMSB(最上位バイト)へのシフトと考える必要があります

次に、質問の例と同様に:

numb=1025

リトル・エンディアン

LSB 00000001 00000100 00000000 00000000 MSB

だから、<< 10されるだろう10bitからLSB MSBにシフトします。


<< 10ステップごとのリトルエンディアン形式の比較と操作:

MSB                                        LSB
    00000000  00000000  00000100  00000001  numb(1025)
    00000000  00010000  00000100  00000000  << 10

LSB                                        MSB
    00000000  00000100  00010000  00000000 numb(1025) << 10, and put in a Little Endian Format

LSB                                        MSB
    00000001  00000100  00000000  00000000 numb(1205) in Little Endian format
    00000010  00001000  00000000  00000000 << 1 
    00000100  00010000  00000000  00000000 << 2 
    00001000  00100000  00000000  00000000 << 3 
    00010000  01000000  00000000  00000000 << 4
    00100000  10000000  00000000  00000000 << 5
    01000000  00000000  00000001  00000000 << 6
    10000000  00000000  00000010  00000000 << 7
    00000000  00000001  00000100  00000000 << 8
    00000000  00000010  00001000  00000000 << 9
    00000000  00000100  00010000  00000000 << 10 (check this final result!)

うわー!OPの説明どおり、期待どおりの結果が得られます。

OPが期待した結果を得られなかった問題は次のとおりです。

  1. 彼はLSBからMSBにシフトしなかったようです。

  2. リトルエンディアン形式でビットをシフトする場合、次のことを理解する必要があります(感謝します)。

LSB 10000000 00000000 MSB << 1
LSB 00000000 00000001 MSB、ではない LSB 01000000 00000000 MSB

個人ごとに8bits、実際にはMSB 00000000 LSBビッグエンディアン形式で記述しているためです。

だからそれは

LSB[ (MSB 10000000 LSB) (MSB 00000000 LSB) ]MSB


総括する:

  1. ビット単位の演算はblablablabla ...で抽象化されていると言われていますが、ビット単位の演算を手動で計算する場合、バイナリ形式を紙に書き留めるときに使用するエンディアンを知る必要があります。また、すべてのオペレーターが同じエンディアンを使用するようにする必要もあります。

  2. OPが期待どおりの結果を得られなかったのは、彼がシフティングを誤ったためです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.