2016-11-16 7 views
0

私は、コンパイラによって生成されたコードを逆アセンブルしている、と私はそれが以下の命令シーケンスを生成していることを参照してください。2で割ると、コンパイラは31ビット右シフトを生成するのはなぜですか?

mov  eax, edx 
shr  eax, 1Fh 
add  eax, edx 
sar  eax, 1 

このコードの目的は何ですか?


私は

sar  eax, 1 

が2で割りますが、

shr  eax, 1Fh 

は何をするのかということを知っていますか?左のビットが0または1の場合、EAXは0または1のいずれかになりますか?

これは私にとって奇妙に見えます。誰か説明できますか?

答えて

2

shr eax, 1Fhは、eaxの最上位ビットを分離するために役立ちます。 16進数1Fhを10進数31に変換すると分かりやすいでしょう。今度は、eaxが31だけ右にシフトしていることがわかります。eaxは32ビットの値なので、ビットを31右にシフトすると、最上位ビットが分離されます。eaxには0または1が含まれます。元の値はビット31でした(0でビットに番号を付けることを前提としています)。

符号ビットを隔離するための一般的なトリックです。値が2の補数のマシンで符号付き整数として解釈されるとき、最上位ビットは符号ビットです。値が負の場合は設定され(== 1)、そうでない場合はクリア(== 0)されます。もちろん、値が符号なし整数として解釈される場合、最上位ビットはその値を格納するために使用されるもう1つのビットであるため、最上位ビットは任意の値を持ちます。


ここでは、分解を通じて行ずつ行くことは、コードが何をするかです:

mov  eax, edx 

明らかに、入力がEDXにありました。この命令はEDXの値をEAXにコピーします。これにより、後続のコードでEAXの値を操作することができます。元の値は失われません(EDX)。

shr  eax, 1Fh 

右31個の場所によるシフトEAX

は、このように最上位ビットを単離します。入力値が符号付き整数であると仮定すると、これが符号ビットになります。 EAXは、元の値が負の場合は1を、そうでない場合は0を含みます。

add  eax, edx 

EAXで私たちの一時的な値を元の値(EDX)を追加します。元の値が負の値の場合は、1が加算されます。それ以外の場合は、0が加算されます。

sar  eax, 1 

1つの場所によってシフトEAX権利を。ここで相違点は、の算術の右シフトであるのに対し、SHRの論理の右シフトであることです。論理的にシフトすると、新たに露出したビットが0で埋められます。算術シフトは、最上位ビット(符号ビット)を新たに露光されたビットにコピーする。

一緒にすべてを置く

これは、負の値が正しく丸みを帯びていることを確認するために2によって署名された整数値を分割するための標準的なイディオムです。

符号なしの値を2で除算すると、単純なビットシフトがすべて必要です。したがって:

shr eax, 1 

しかし、符号付きの値を分割する際、あなたは符号ビットに対処する必要があります。

unsigned Foo(unsigned value) 
{ 
    return (value/2); 
} 

は同等です。 sar eax, 1を使用して符号付き整数の除算を2で実装できますが、結果の値は負の無限大に丸められます。これは、常にゼロに向かって丸めるDIV/IDIV命令の動作とは異なります。ゼロへ向かう振る舞いをエミュレートしたい場合は、特別な処理が必要です。これは、コードが持つものとまったく同じです。あなたは次の関数をコンパイルするときに実際には、GCC、クラン、MSVC、そしておそらく他のすべてのコンパイラは、すべて正確にこのコードを生成します。

int Foo(int value) 
{ 
    return (value/2); 
} 

これは非常に古いトリックです。 Michael Abrashは、アセンブリ言語ののZenでそれについて議論しました。 1990年(Here is the relevant section)の書籍に掲載されています。それ以前はアセンブリ言語の専門家の間では確かによく知っていました。

関連する問題