9

x86 または x86_64 アーキテクチャでの除算オーバーフロー エラーについていくつか質問があります。最近、整数オーバーフローについて読んでいます。通常、算術演算の結果が整数オーバーフローになると、FLAGS レジスタのキャリー ビットまたはオーバーフロー ビットがセットされます。しかし、どうやらこの記事によると、除算によるオーバーフローはオーバーフロー ビットを設定せず、0 で除算する場合と同様にハードウェア例外をトリガーします。

現在、除算による整数オーバーフローは、乗算よりもはるかにまれです。除算オーバーフローをトリガーする方法はわずかしかありません。1 つの方法は、次のようなことです。

int16_t a = -32768;
int16_t b = -1;
int16_t c = a / b;

この場合、符号付き整数の 2 の補数表現により、正の 32768 を符号付き 16 ビット整数で表すことができないため、除算演算がオーバーフローし、-32768 という誤った値が発生します。

いくつかの質問:

1) この記事の内容に反して、上記はハードウェア例外を引き起こしませんでした。Linux を実行している x86_64 マシンを使用しています。ゼロで除算すると、プログラムはFloating point exception. しかし、除算のオーバーフローを引き起こすと、プログラムは通常通り続行し、誤った商を黙って無視します。では、なぜこれがハードウェア例外を引き起こさないのでしょうか?

2) 他の算術オーバーフローとは対照的に、除算エラーがハードウェアによって非常に厳しく扱われるのはなぜですか? 乗算オーバーフロー (偶発的に発生する可能性がはるかに高い) がハードウェアによって暗黙のうちに無視されるのはなぜですか? 除算オーバーフローは致命的な割り込みをトリガーするはずです?

===========編集==============

わかりました、皆さん、回答ありがとうございます。基本的に、上記の 16 ビット整数除算は、商がまだレジスタ サイズよりも小さいため、ハードウェア障害を引き起こすべきではないという回答を受け取りました。私はこれを理解していません。この場合、商を格納するレジスタは 16 ビットです。これは符号付きの正の 32768 を格納するには小さすぎます。では、なぜハードウェア例外が発生しないのでしょうか?

よし、これを GCC インライン アセンブリで直接実行して、何が起こるか見てみましょう。

int16_t a = -32768;
int16_t b = -1;

__asm__
(
    "xorw %%dx, %%dx;"            // Clear the DX register (upper-bits of dividend)
    "movw %1, %%ax;"              // Load lower bits of dividend into AX
    "movw %2, %%bx;"              // Load the divisor into BX
    "idivw %%bx;"                 // Divide a / b (quotient is stored in AX)
    "movw %%ax, %0;"              // Copy the quotient into 'b'
    : "=rm"(b)                    // Output list
    :"ir"(a), "rm"(b)             // Input list
    :"%ax", "%dx", "%bx"          // Clobbered registers
);

printf("%d\n", b);

これは単に誤った値を出力します: -32768. 商 (AX) を格納するレジスタが小さすぎて商に収まらない場合でも、ハードウェア例外は発生しません。したがって、ここでハードウェア障害が発生しない理由がわかりません。

4

7 に答える 7

17

C 言語では、 より小さい型内で算術演算が実行されることはありませんint。より小さなオペランドで算術演算を試みるときはいつでも、最初にそれらを に変換する整数昇格の対象となりintます。たとえば、プラットフォームintが 32 ビット幅の場合、C プログラムに強制的に 16 ビット除算を実行させる方法はありません。代わりに、コンパイラは 32 ビット除算を生成します。これがおそらく、C の実験で除算で予想されるオーバーフローが発生しない理由です。お使いのプラットフォームに実際に 32 ビット がある場合は、32 ビット オペランド (つまり で割る)でint同じことを試すのが最善の策です。最終的には、C コードでもオーバーフロー例外を再現できると確信しています。INT_MIN-1


BXのオペランドとして指定したため、アセンブリ コードでは 16 ビット除算を使用していますidiv。x86 での 16 ビット除算は、DX:AXペアに格納された 32 ビット被除数をidivオペランドで除算します。これは、コードで行っていることです。このDX:AXペアは、1 つの複合 32 ビット レジスタとして解釈されます。つまり、このペアの符号ビットは、実際には の最上位ビットになりDXます。の最上位ビットはAX符号ビットではなくなりました。

そして、あなたは何をしましたDXか?あなたは単にそれをクリアしました。0 に設定します。ただし、 0 に設定すると、被除数はDXとして解釈されます。マシンの観点からは、このようなペアは実際にはの値を表します。つまり、アセンブリ言語の実験では、 で割っています。そして結果は、あるべき姿です。ここでは珍しいことは何もありません。DX:AX+32768+32768-1-32768

-32768ペアで表現したい場合は、それを符号拡張する必要があります。つまり、ゼロの代わりにすべて 1 のビット パターンでDX:AX埋める必要があります。DX行う代わりに、で初期化してから doneにxor DX, DXする必要があります。それは に符号拡張されます。AX-32768cwdAXDX

たとえば、私の実験(GCCではない)では、このコード

__asm  {
  mov AX, -32768
  cwd
  mov BX, -1
  idiv BX
}

実際に で除算しようとするため、予想される例外が発生-32768-1ます。

于 2010-10-08T16:47:56.193 に答える
2

整数 2 の補数の加算/減算/乗算で整数オーバーフローが発生した場合でも、有効な結果が得られます。上位ビットが欠落しているだけです。この動作は便利な場合が多いため、例外を生成することは適切ではありません。

ただし、整数除算では、ゼロによる除算の結果は役に立ちません (浮動小数点とは異なり、2 の補数の整数には INF 表現がないため)。

于 2010-10-08T16:20:59.227 に答える
1

この記事の内容に反して、上記はハードウェア例外を引き起こしませんでした

記事はそうは言っていません。は言う

...ソースオペランド(除数)がゼロの場合、または商が指定されたレジスタに対して大きすぎる場合、除算エラーを生成します

レジスタのサイズが 16 ビット (32 || 64) よりも確実に大きい

于 2010-10-08T16:30:09.110 に答える
1

整数オーバーフローに関する関連セクションから

add、mul、および imul 命令とは異なり、インテルの除算命令 div および idiv はオーバーフロー フラグを設定しません。ソースオペランド (除数) がゼロの場合、または指定されたレジスタに対して商が大きすぎる場合、除算エラーが生成されます。

レジスタのサイズは、最新のプラットフォームでは 32 ビットまたは 64 ビットです。32768 は、これらのレジスタの 1 つに収まります。ただし、次のコードは整数オーバーフローの実行をスローする可能性が非常に高くなります (VC8 のコア Duo ラップトップでは発生します)。

int x= INT_MIN;
int y= -1;
int z= x/y;
于 2010-10-08T16:34:39.613 に答える
0

一部の古いコンピューターでは、ゼロで除算しようとすると、いくつかの深刻な問題が発生すると推測します(たとえば、ハードウェアを十分に減算しようとする無限のサイクルに入れて、オペレーターが問題を修正するまで、余りが配当より少なくなるようにします)。 )、これにより、整数のオーバーフローよりも深刻な障害と見なされる除算オーバーフローの伝統が始まりました。

プログラミングの観点から、予期しない除算オーバーフローが予期しない整数オーバーフロー(符号付きまたは符号なし)よりも多かれ少なかれ深刻である必要がある理由はありません。除算のコストを考えると、後でオーバーフローフラグをチェックする限界費用はかなりわずかです。伝統は私がハードウェアトラップを持っているために見ることができる唯一の理由です。

于 2010-10-08T18:01:59.840 に答える
0

32 ビットの実装でintは、この例では除算オーバーフローは発生しません。その結果、完全に表現可能なint32768 が生成されint16_t、割り当て時に実装定義の方法で変換されます。これは、C 言語で指定されたデフォルトの昇格によるものであり、その結果、ここで例外を発生させる実装は非準拠になります。

例外を発生させたい場合 (実際には発生する場合と発生しない場合がありますが、実装次第です)、次のことを試してください。

int a = INT_MIN, b = -1, c = a/b;

コンパイラがコンパイル時に最適化するのを防ぐために、いくつかのトリックを実行する必要がある場合があります。

于 2010-10-08T17:31:01.367 に答える
0
  1. あなたの例がハードウェア例外を生成しなかった理由は、C の整数昇格規則によるものです。より小さいオペランドは、操作が実行される前にint自動的にプロモートされます。ints

  2. さまざまな種類のオーバーフローが異なる方法で処理される理由については、x86 マシン レベルでは乗算オーバーフローのようなものは実際には存在しないことを考慮してください。AX を他のレジスタで乗算すると、結果は DX:AX のペアになるため、常に結果の余地があり、オーバーフロー例外を通知する機会はありません。ただ、C言語などでは2の積がintsに収まるはずintなので、Cレベルでオーバーフローすることはあります。x86 ではMULs に OF (オーバーフロー フラグ) が設定されることがありますが、これは単に結果の上位部分がゼロ以外であることを意味します。

于 2010-10-08T16:54:26.213 に答える