なぜ符号なし整数オーバーフローは動作が定義されていますが、符号付き整数オーバーフローは定義されていないのですか?


209

符号なし整数オーバーフローは、CおよびC ++標準の両方で明確に定義されています。たとえば、C99標準§6.2.5/9)は

結果の符号なし整数型で表すことができない結果は、結果の型で表すことができる最大値よりも1大きい数を法として減じられるため、符号なしオペランドを伴う計算は決してオーバーフローできません。

ただし、どちらの規格も、符号付き整数オーバーフローは未定義の動作であると述べています。ここでも、C99標準(§3.4.3/1)から

未定義の動作の例は、整数オーバーフローでの動作です

この矛盾の歴史的または(さらに良い!)技術的な理由はありますか?


50
おそらく、符号付き整数を表す方法が複数あるためです。標準では、少なくともC ++では、どちらの方法が指定されていません。
juanchopanza 2013


7
フアンチョパンザが言ったことは理にかなっている。私が理解しているように、オリジナルのC標準は、大部分が既存の慣行を成文化したものです。その時点でのすべての実装が、署名されていない「オーバーフロー」が何をすべきかについて合意した場合、それはそれを標準化する正当な理由です。署名されたオーバーフローが何をすべきかについて彼らは同意しなかったので、それは標準には入らなかった。

2
@DavidElliman追加時の符号なしラップアラウンドも簡単に検出できます(if (a + b < a))。乗算のオーバーフローは、符号付きタイプと符号なしタイプの両方で困難です。

5
@DavidElliman:検出できるかどうかだけでなく、結果はどうなるかという問題です。符号+値の実装では、MAX_INT+1 == -02の補数で、それは次のようになりながらINT_MIN
デビッド・ロドリゲス- dribeas

回答:


163

歴史的な理由は、ほとんどのC実装(コンパイラ)が、使用する整数表現で実装するのが最も簡単なオーバーフロー動作を使用しただけであるということです。Cの実装は通常、CPUが使用するのと同じ表現を使用していました。そのため、オーバーフロー動作は、CPUが使用する整数表現から続きました。

実際には、実装によって異なる場合があるのは、1の補数、2の補数、符号の大きさなど、符号付きの値の表現だけです。符号なしの型の場合、明白なバイナリ表現が1つしかないため、標準でバリエーションを許可する理由はありません(標準ではバイナリ表現しか許可されていません)。

関連する引用:

C99 6.2.6.1:3

unsignedビットフィールドに格納された値とタイプunsigned charのオブジェクトは、純粋なバイナリ表記を使用して表されます。

C99 6.2.6.2:2

符号ビットが1の場合、値は次のいずれかの方法で変更されます。

—符号ビット0の対応する値は否定されます(符号と大きさ)。

—符号ビットの値は−(2 N)(2の補数)です。

—符号ビットの値は−(2 N − 1)(1の補数)です。


現在、すべてのプロセッサは2の補数表現を使用していますが、符号付き算術オーバーフローは未定義のままであり、コンパイラメーカーはこの未定義を使用して最適化を支援するため、未定義のままにしたいと考えています。たとえば、Ian Lance Taylorによるこのブログ投稿またはAgner Fogによるこの苦情、および彼のバグレポートへの回答を参照してください。


6
ただし、ここで重要なのは、2の補数の符号付き演算以外を使用するアーキテクチャが現代の世界に残っていないことです。言語標準がまだPDP-1などでの実装を許可していることは、純粋に歴史的な成果物です。
アンディロス

9
@AndyRossですが、2013年の時点で1の補数と新しいリリースを備えたシステム(OS +コンパイラ、確かに古い歴史を持つ)があります。例:OS 2200
ouah

3
@Andy Rossは、「アーキテクチャなし... 2の補数以外のものを使用する...」と見なしますか?今日、DSPと組み込みプロセッサの全範囲が含まれますか?
chux-モニカを2013年

11
@AndyRoss:2の補数以外を使用する「no」アーキテクチャ(「no」の定義の一部)がありますが、符号付き整数に飽和演算を使用するDSPアーキテクチャは確実に存在します。
Stephen Canon

10
飽和算術演算は、確実に標準に準拠しています。もちろん、ラッピング命令は符号なしの算術演算に使用する必要がありますが、コンパイラは常に、符号なしまたは符号付き演算が行われているかどうかを知るための情報を持っているため、命令を確実に適切に選択できます。
カフェ

15

Pascalの良い答え(主な動機は確かですが)を除いて、一部のプロセッサが符号付き整数オーバーフローで例外を引き起こす可能性もあります。もちろん、コンパイラーが「別の動作を準備する」必要がある場合、問題が発生します(たとえば、潜在的なオーバーフローをチェックし、その場合は別の方法で計算するために、追加の命令を使用します)。

また、「未定義の動作」が「動作しない」という意味ではないことにも注意してください。これは、実装がその状況で好きなことを何でもできることを意味します。これには、「正しいこと」のほか、「警察に電話する」または「衝突する」ことも含まれます。ほとんどのコンパイラは、可能であれば、比較的簡単に定義できる(この場合はそうです)と仮定して、「正しいことを行う」ことを選択します。ただし、計算でオーバーフローが発生している場合は、実際に結果がどうなるかを理解することが重要です。また、コンパイラーが期待とは異なる動作を行う可能性があります(これは、コンパイラーのバージョン、最適化設定などに大きく依存する場合があります)。 。


23
ただし、コンパイラーは、正しいことを実行することに依存してほしくないのでint f(int x) { return x+1>x; }、最適化を使用してコンパイルするとすぐに、そのほとんどが表示されます。GCCとICCは、デフォルトのオプションで、上記をに最適化しますreturn 1;
Pascal Cuoq 2013

1
int最適化レベルに応じてオーバーフローに直面したときに異なる結果をもたらすプログラム例については、ideone.com / cki8nMを参照してください。これは、あなたの答えが悪いアドバイスを与えていることを示していると思います。
Magnus Hoff

その部分を少し修正しました。
Mats Petersson 2013

Cが「符号付き2の補数のラッピング」整数を宣言する手段を提供する場合、Cを実行できるプラットフォームで、少なくとも適度に効率的にそれをサポートするのに多くの問題が発生することはありません。余分なオーバーヘッドがあれば、ラッピング動作が不要な場合にコードでこのような型を使用する必要はありませんが、2の補数の整数に対するほとんどの演算は、比較と昇格を除いて、符号なし整数に対する演算と同じです。
スーパーキャット2014

1
コンパイラーが正しく機能するには、負の値が存在し、「機能」する必要があります。プロセッサー内の符号付き値の欠如を回避し、符号なしの値を1の補数または2の補数として使用することはもちろん可能です。命令セットが何であるかに基づく感覚。通常、これを行うには、ハードウェアでサポートするよりも大幅に遅くなりますが、ハードウェアで浮動小数点をサポートしていないプロセッサなどと同じです。追加のコードが多く追加されるだけです。
Mats Petersson、2015年

10

まず、C11 3.4.3は、すべての例や脚注と同様に、規範的なテキストではないため、引用には関係がないことに注意してください。

整数と浮動小数点数のオーバーフローが未定義の動作であることを示す関連テキストは次のとおりです。

C11 6.5 / 5

式の評価中に例外条件が発生した場合(つまり、結果が数学的に定義されていないか、その型の表現可能な値の範囲にない場合)、動作は未定義です。

符号なし整数型の動作に関する明確な説明は、ここにあります:

C11 6.2.5 / 9

符号付き整数型の非負の値の範囲は、対応する符号なし整数型の部分範囲であり、各型の同じ値の表現は同じです。結果の符号なし整数型で表すことができない結果は、結果の型で表すことができる最大値よりも1大きい数を法として減じられるため、符号なしオペランドを含む計算はオーバーフローすることはありません。

これにより、符号なし整数型が特別なケースになります。

また、いずれかの型が符号付きの型に変換され、古い値を表すことができない場合は、例外があることにも注意してください。信号は発生する可能性がありますが、動作は単に実装によって定義されます。

C11 6.3.1.3

6.3.1.3符号付きおよび符号なし整数

整数型の値が_Bool以外の別の整数型に変換されるとき、値が新しい型で表現できる場合、値は変更されません。

それ以外の場合、新しいタイプが符号なしの場合、値が新しいタイプの範囲内になるまで、新しいタイプで表すことができる最大値よりも1だけ多く加算または減算することによって値が変換されます。

そうでない場合、新しいタイプは署名され、値をそのタイプで表すことができません。結果は実装定義であるか、実装定義の信号が発生します。


6

他の問題に加えて、符号なしの数学のラップは、符号なし整数型は、値の任意のペアのために、他のものの間で抽象代数群(つまりを意味する、として振る舞う作る持つ、言及XおよびY他のいくつかの値が存在するだろう、ZなどというX+Z意志、もし適切にキャスト、等しいYY-Z正しくキャストされた場合は等しいX)。符号なしの値が単に格納場所の型であり、中間式の型ではない場合(たとえば、最大の整数型に相当する符号なしの型がなく、符号なしの型に対する算術演算は、最初にそれらをより大きな符号付きの型に変換したかのように動作した場合、定義されたラッピング動作の必要性はそれほど高くありませんが、たとえば、追加の逆行列を持たない型で計算を行うことは困難です。

これは、たとえば、TCPシーケンス番号や特定のアルゴリズム(ハッシュ計算など)で、ラップアラウンド動作が実際に役立つ状況で役立ちます。計算を実行してオーバーフローするかどうかを確認することは、特に計算に使用可能な最大の整数型が含まれる場合、オーバーフローするかどうかを事前に確認するよりも簡単なことが多いため、オーバーフローを検出する必要がある状況でも役立ちます。


私は完全には従いません-なぜ相加的逆数を持つことが役立つのですか?オーバーフローの動作が実際に役立つ状況は考えられません...
sleske

@sleske:人間が読みやすいように10進数を使用して、エネルギーメーターが0003を読み取り、以前の読み取り値が9995だった場合、-9992単位のエネルギーが使用されたか、または0008単位のエネルギーが使用されたことを意味しますか?0003-9995の利回り0008があると、後者の結果を簡単に計算できます。これを-9992にすると、少し厄介になります。それはあまりだと、それはどちらか、しかし、それは必要な9995に0003を比較することになるだろうか通知を持って逆減算を行い、9999からその結果を差し引くと、1を追加することができない
supercat

@sleske:連想、分散、可換の算術の法則を適用して式を書き換え、それらを簡略化できることは、人間とコンパイラの両方にとって非常に便利です。例えば、発現があればa+b-c、ループ内で計算されるが、bそしてcそのループ内で一定であり、それはの計算移動する人であってもよい(b-c)ループ外に、それを行うことは、とりわけ必要とするであろう(b-c)、に付加価値をもたらしますa、はa+b-c、を生成します。これcは、追加の逆行列を持つ必要があります。
スーパーキャット2016年

:説明ありがとうございます。私がそれを正しく理解していれば、すべての例では、オーバーフローを実際に処理したいと想定しています。私が遭遇したほとんどの場合、オーバーフローは望ましくないので、オーバーフローを使用した計算の結果は役に立たないので、それを防ぎたいと思います。たとえば、エネルギーメーターの場合、オーバーフローが発生しないタイプを使用することをお勧めします。
sleske 2016年

1
... の算術値が型内で表現可能であるかどうかに(a+b)-c等しいため、の値の可能な範囲に関係なく、置換は有効になります。a+(b-c)b-c(b-c)
スーパーキャット2016年

1

おそらく、符号なし算術が定義されるもう1つの理由は、符号なし数値が2 ^ nを法とする整数を形成するためです。ここで、nは符号なし数値の幅です。符号なしの数値は、10進数ではなく2進数を使用して表される単なる整数です。モジュラスシステムで標準操作を実行することはよく理解されています。

OPの引用はこの事実に言及していますが、符号なし整数を2進数で表すには、明確で論理的な方法が1つしかないという事実も強調しています。対照的に、符号付き数値は2の補数を使用して表されることが最も多いですが、標準(セクション6.2.6.2)に記載されているように他の選択も可能です。

2の補数表現により、特定の演算をバイナリ形式でより意味のあるものにすることができます。たとえば、負の数の増分は、正の数の場合と同じです(オーバーフロー条件で期待)。マシンレベルの一部の操作は、符号付きの数値と符号なしの数値で同じです。ただし、これらの演算の結果を解釈する場合、正と負のオーバーフローで意味をなさない場合があります。さらに、オーバーフローの結果は、基になる符号付き表現によって異なります。


構造体がフィールドになるためには、加法恒等式以外の構造体のすべての要素が乗法的逆行列を持っている必要があります。Nが1または素数の場合にのみ、mod Nが一致する整数の構造体がフィールドになります(N == 1の場合は無視フィールド)。私が私の答えであなたが私が逃したと感じる何かがありますか?
スーパーキャット2017年

あなたが正しいです。プライムパワーモジュライに混乱しました。元の回答を編集しました。
2017年

ここでさらに混乱するのは、次数2 ^ nのフィールドがあることです。これは、2 ^ nを法とする整数に環同型ではありません。
Kevin Ventullo

また、2 ^ 31-1はメルセンヌ素数です(ただし、2 ^ 63-1は素数ではありません)。したがって、私の元のアイデアは台無しにされました。また、当時は整数のサイズが異なっていました。したがって、私の考えはせいぜい修正主義者でした。
2018年

符号なし整数は(フィールドではなく)リングを形成し、低次部分もリングを生成し、値全体に対して操作を実行してから切り捨てると、下部だけで操作を実行するのと同等の動作になります。ほぼ間違いなく考慮事項です。
スーパーキャット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.