符号なし変数(特にnsigned short int
)が整数オーバーフローでいわゆるラップアラウンドを実行するというC仕様のビットを読みましたが、できませんでした。 未定義の振る舞いを残した以外は、符号付き変数で何かを見つけます。
私の教授は、それらの値もラップアラウンドされると私に言いました(多分彼は単にgccを意味していました)。ビットが切り捨てられて、残ったビットが奇妙な値を与えると思いました!
ラップアラウンドとは何であり、ビットを切り捨てるだけとはどのように異なりますか。
符号付き整数変数には、C言語でのラップアラウンド動作はありません。算術計算中の符号付き整数オーバーフローは未定義の振る舞いを生成します。ところで、あなたが言及したGCCコンパイラは、最適化で厳密なオーバーフローセマンティクスを実装することで知られています。つまり、このような未定義の動作状況によって提供される自由を利用します。GCCコンパイラは、符号付き整数値がラップアラウンドしないことを前提としています。つまり、GCCは、実際には、符号付き整数型のラップアラウンド動作に依存するコンパイラの1つであるということですできません。
たとえば、GCCコンパイラは、変数int i
に対して次の条件を想定できます。
if (i > 0 && i + 1 > 0)
単なるに相当します
if (i > 0)
これはまさに厳密なオーバーフローセマンティクスの意味です。
符号なし整数型は、モジュロ演算を実装します。モジュロは2^N
に等しくなります。ここで、N
は型の値表現のビット数です。このため、符号なし整数型は実際にオーバーフロー時にラップアラウンドしているように見えます。
ただし、C言語は、int
/unsigned int
よりも小さいドメインで算術計算を実行することはありません。質問で言及したタイプunsigned short int
は、通常、計算を開始する前に式でタイプint
にプロモートされます(unsigned short
の範囲がint
)。つまり、1)unsigned short int
を使用した計算はint
のドメインで実行され、int
がオーバーフローするとオーバーフローが発生します。2)このような計算中にオーバーフローすると、未定義の動作が発生します。 、動作をラップアラウンドしないでください。
たとえば、このコードはラップアラウンドを生成します
unsigned i = USHRT_MAX;
i *= INT_MAX; /* <- unsigned arithmetic, overflows, wraps around */
このコードは
unsigned short i = USHRT_MAX;
i *= INT_MAX; /* <- signed arithmetic, overflows, produces undefined behavior */
未定義の動作につながります。
int
オーバーフローが発生せず、結果がunsigned short int
タイプに変換された場合、値がラップアラウンドされたかのように見える2^N
を法として再び削減されます。
幅がわずか3ビットのデータ型があるとします。これにより、0から7までの8つの異なる値を表すことができます。1から7を追加すると、値8(1000)を表すのに十分なビットがないため、0に「ラップアラウンド」します。
この動作は、符号なしタイプに対して明確に定義されています。符号付きの値を表すには複数のメソッドがあり、オーバーフローの結果はそのメソッドに基づいて異なる方法で解釈されるため、符号付きの型に対してはnot明確に定義されています。
符号の大きさ:最上部のビットは符号を表します。正の場合は0、負の場合は1。私のタイプが再び3ビット幅の場合、次のように符号付きの値を表すことができます。
000 = 0
001 = 1
010 = 2
011 = 3
100 = -0
101 = -1
110 = -2
111 = -3
符号に1ビットが使用されるため、0から3までの値をエンコードするためのビットは2ビットしかありません。1から3を加算すると、結果として-0でオーバーフローします。はい、0には2つの表現があります。1つは正、もう1つは負です。符号と大きさの表現にそれほど頻繁に遭遇することはありません。
1の補数:負の値は、正の値のビット単位の逆数です。ここでも、3ビットタイプを使用します。
000 = 0
001 = 1
010 = 2
011 = 3
100 = -3
101 = -2
110 = -1
111 = -0
値をエンコードするためのビットが3つありますが、範囲は[-3、3]です。 1を3に追加すると、結果として-3でオーバーフローします。これは、上記の符号の大きさの結果とは異なります。この場合も、この方法を使用した0には2つのエンコーディングがあります。
2の補数:負の値は、正の値のビット単位の逆数に1を加えたものです。3ビットシステムの場合:
000 = 0
001 = 1
010 = 2
011 = 3
100 = -4
101 = -3
110 = -2
111 = -1
1を3に追加すると、結果として-4でオーバーフローします。これは、前の2つの方法とは異なります。値の範囲がわずかに広く[-4、3]であり、0の表現は1つだけであることに注意してください。
2の補数は、符号付き値を表す最も一般的な方法ですが、それだけではありません。したがって、C標準では、符号付き整数型をオーバーフローしたときに何が起こるかを保証できません。したがって、動作はndefinedのままになるため、コンパイラは複数の表現の解釈を処理する必要がありません。
未定義の振る舞いは、符号付き整数型が符号と大きさ、1の補数、または2の補数のいずれかとして表される可能性がある初期の移植性の問題に起因します。
現在、すべてのアーキテクチャは、整数を2の補数として表します。ただし、注意してください。コンパイラは未定義の動作を実行しないと想定しているため、最適化をオンにすると奇妙なバグが発生する可能性があります。
符号付き8ビット整数では、ラップアラウンドの直感的な定義は、2の補数バイナリ0111111(127)と1000000(-128)で+127から-128に変化するように見える場合があります。ご覧のとおり、これは、符号付きまたは符号なしの整数を表すとは見なさずに、バイナリデータをインクリメントする自然な進歩です。直感に反して、実際のオーバーフローは、符号なし整数のラップアラウンドの意味で-1(11111111)から0(00000000)に移動するときに発生します。
これは、標準による「正しい」動作がないため、符号付き整数がオーバーフローした場合の正しい動作が何であるかというより深い質問には答えません。