符号なし整数オーバーフローは、C標準とC++標準の両方で適切に定義されています。たとえば、 C99標準 (§6.2.5/9
)状態
結果の符号なし整数型で表現できない結果は、結果の型で表現できる最大値より1大きい数でモジュロ化されるため、符号なしオペランドを含む計算は決してオーバーフローしません。
ただし、両方の標準は、符号付き整数オーバーフローは未定義の動作であると述べています。繰り返しますが、C99標準から(§3.4.3/1
)
未定義の動作の例は、整数オーバーフローの動作です
この矛盾の歴史的または(さらに良い!)技術的な理由はありますか?
歴史的な理由は、ほとんどのC実装(コンパイラ)は、使用した整数表現で実装するのが最も簡単なオーバーフロー動作を使用したためです。 C実装は通常、CPUが使用するのと同じ表現を使用しました。したがって、オーバーフロー動作は、CPUが使用する整数表現に続きます。
実際には、実装によって異なる場合があるのは符号付きの値の表現のみです。1の補数、2の補数、符号の大きさです。符号なしの型の場合、明白なバイナリ表現が1つしかないため、標準がバリエーションを許可する理由はありません(標準ではバイナリ表現のみが許可されます)。
関連する引用:
C99 6.2.6.1:3:
符号なしビットフィールドおよびunsigned char型のオブジェクトに格納されている値は、純粋なバイナリ表記を使用して表されます。
C99 6.2.6.2:2:
符号ビットが1の場合、値は次のいずれかの方法で変更されます。
—符号ビット0の対応する値は否定されます(sign anditude);
—符号ビットの値は 2N)(2の補数);
—符号ビットの値は-(2N − 1)(1の補数)。
現在、すべてのプロセッサは2の補数表現を使用していますが、符号付き算術オーバーフローは未定義のままであり、コンパイラメーカーは最適化を支援するためにこの未定義を使用するため、未定義のままにする必要があります。たとえば、この---(ブログ投稿 Ian Lance Taylor、またはこの 苦情 Agner Fog、および彼のバグレポートへの回答を参照してください。
Pascalの良い答え(主な動機だと確信しています)の他に、いくつかのプロセッサは符号付き整数オーバーフローで例外を引き起こす可能性があり、もちろんコンパイラが「別の動作を手配」しなければならない場合に問題を引き起こす可能性があります(たとえば、余分な命令を使用して潜在的なオーバーフローをチェックし、その場合は異なる方法で計算します)。
「未定義の動作」は「機能しない」という意味ではないことにも注意してください。これは、その状況で実装が好きなことを何でも行えることを意味します。これには、「正しいこと」を行うことと、「警察に電話する」または「クラッシュする」ことが含まれます。ほとんどのコンパイラは、可能であれば、「正しいことをする」を選択します。これは比較的簡単に定義できます(この場合はそうです)。ただし、計算でオーバーフローが発生している場合は、実際に結果が何であるかを理解することが重要です。また、コンパイラは予想外のことを行う場合があります(これは、コンパイラのバージョン、最適化設定などに大きく依存する場合があります) 。
まず第一に、C11 3.4.3は、すべての例や脚注と同様、規範的なテキストではないため、引用には関係がないことに注意してください!
整数および浮動小数点数のオーバーフローが未定義の動作であることを示す関連テキストは次のとおりです。
C11 6.5/5
式の評価中に例外条件が発生した場合(つまり、結果が数学的に定義されていないか、そのタイプの表現可能な値の範囲にない場合)、動作は未定義です。
符号なし整数型の動作に関する明確な説明は、ここにあります。
C11 6.2.5/9
符号付き整数型の負でない値の範囲は、対応する符号なし整数型の部分範囲であり、各型の同じ値の表現は同じです。結果の符号なし整数型で表現できない結果は、結果の型で表現できる最大値よりも1大きい数を法として減少するため、符号なしオペランドを含む計算はオーバーフローすることはありません。
これにより、符号なし整数型は特別なケースになります。
また、いずれかの型が符号付き型にconvertedであり、古い値を表すことができない場合は例外があることに注意してください。その場合、シグナルは発生する可能性がありますが、動作は単に実装で定義されます。
C11 6.3.1.
6.3.1.3符号付き整数と符号なし整数
整数型の値が_Bool以外の別の整数型に変換されるとき、値が新しい型で表現できる場合、その値は変更されません。
それ以外の場合、新しいタイプが符号なしの場合、値は、新しいタイプの範囲内になるまで、新しいタイプで表現できる最大値よりも1多い値を繰り返し加算または減算することにより変換されます。
それ以外の場合、新しい型は署名され、値はその型で表現できません。結果は実装定義であるか、実装定義信号が発生します。
言及された他の問題に加えて、符号なしの数学ラップがあると、符号なし整数型は抽象代数群として振る舞います(つまり、X
とY
のペアに対して、他の値Z
は、X+Z
が適切にキャストされるとY
に等しく、Y-Z
は適切にキャストされるとX
に等しくなります。符号なしの値が中間位置型ではなく単なる記憶場所の型である場合(たとえば、最大の整数型に相当する符号なしの型がなく、符号なしの型の算術演算が最初に大きな符号付き型に変換されたように動作する場合)、定義されたラッピング動作はそれほど必要ではありませんが、たとえば加法逆を持たない型で計算を行うことは困難です。
これは、ラップアラウンド動作が実際に役立つ状況で役立ちます。たとえば、TCPシーケンス番号や、ハッシュ計算などの特定のアルゴリズムを使用する場合に役立ちます。また、計算を実行してオーバーフローしたかどうかを確認する方が、オーバーフローが発生するかどうかを事前に確認するよりも簡単な場合が多いため、オーバーフローを検出する必要がある場合にも役立ちます。
おそらく、符号なし算術が定義されるもう1つの理由は、符号なし数値が2 ^ nを法とする整数を形成するためです。ここで、nは符号なし数値の幅です。符号なしの数値は、10進数ではなく2進数を使用して表される単純な整数です。モジュラスシステムで標準操作を実行することはよく理解されています。
OPの引用はこの事実に言及していますが、符号なし整数をバイナリで表現する明確な論理的方法が1つしかないという事実も強調しています。対照的に、符号付きの数値は2の補数を使用して表されることがほとんどですが、標準(セクション6.2.6.2)で説明されているように、他の選択肢も可能です。
2の補数表現により、特定の操作がバイナリ形式でより意味をなすようになります。たとえば、負の数値をインクリメントすることは、正の数値と同じです(オーバーフロー条件の下で期待)。マシンレベルでの一部の操作は、符号付き数値と符号なし数値で同じにすることができます。ただし、これらの操作の結果を解釈する場合、意味がわからない場合があります-正および負のオーバーフロー。さらに、オーバーフローの結果は、基になる符号付き表現によって異なります。