Cでの符号付きから符号なしへの変換-常に安全ですか?


135

次のCコードがあるとします。

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

ここではどのような暗黙の変換が行われており、このコードはuとのすべての値に対して安全iですか?(安全です。この例の結果が巨大な正の数値にオーバーフローしても、それをintにキャストして実際の結果を得ることができるという意味です。)

回答:


223

短い答え

あなたがiされる変換追加することによって、符号なし整数にUINT_MAX + 1次に添加が大で、その結果、符号なしの値を用いて行われる、result(の値に依存uし、i)。

長い答え

C99標準によると:

6.3.1.8通常の算術変換

  1. 両方のオペランドが同じタイプの場合、それ以上の変換は必要ありません。
  2. それ以外の場合、両方のオペランドが符号付き整数型または両方が符号なし整数型を持つ場合、整数変換ランクの小さいタイプのオペランドは、ランクの大きいオペランドのタイプに変換されます。
  3. それ以外の場合、符号なし整数型のオペランドのランクが他のオペランドの型のランク以上である場合、符号付き整数型のオペランドは、符号なし整数型のオペランドの型に変換されます。
  4. それ以外の場合、符号付き整数型のオペランドの型が符号なし整数型のオペランドの型のすべての値を表すことができる場合、符号なし整数型のオペランドは符号付き整数型のオペランドの型に変換されます。
  5. それ以外の場合、両方のオペランドは、符号付き整数型のオペランドの型に対応する符号なし整数型に変換されます。

あなたの場合、1つのunsigned int(u)とsigned int(i)があります。上記の(3)を参照すると、両方のオペランドのランクが同じであるため、符号なし整数に変換iする必要があります。

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

  1. 整数型の値が_Bool以外の別の整数型に変換されるとき、値が新しい型で表現できる場合、値は変更されません。
  2. それ以外の場合、新しいタイプが符号なしの場合、値が新しいタイプの範囲内になるまで、新しいタイプで表すことができる最大値よりも1だけ多く加算または減算することによって値が変換されます。
  3. そうでない場合、新しいタイプは署名され、値をそのタイプで表すことができません。結果は実装定義であるか、実装定義の信号が発生します。

ここで、上記の(2)を参照する必要があります。をi追加すると、は符号なしの値に変換されますUINT_MAX + 1。したがって、結果はUINT_MAX実装での定義方法に依存します。大きくなりますが、次の理由でオーバーフローしません。

6.2.5(9)

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

ボーナス:算術変換Semi-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

このリンクを使用して、これをオンラインで試すことができます:https : //repl.it/repls/QuickWhimsicalBytes

ボーナス:算術変換の副作用

算術変換規則を使用してUINT_MAX、符号なしの値をに初期化することにより、の値を取得できます-1。つまり、

unsigned int umax = -1; // umax set to UINT_MAX

これは、上記の変換ルールにより、システムの符号付き数値表現に関係なく、移植可能であることが保証されています。詳細については、このSOの質問を参照してください:-1を使用してすべてのビットをtrueに設定しても安全ですか?


なぜそれが単純に絶対値を実行できず、正の数と同様に符号なしとして扱うことができないのか理解できませんか?
ホセサルバティエラ2013

7
@ D.Singhは、回答の間違った部分を指摘していただけますか?
Shmil The Cat

符号付きから符号なしに変換するために、符号なしの値の最大値(UINT_MAX +1)を追加します。同様に、署名なしから署名済みに変換する簡単な方法は何ですか?与えられた数を最大値(unsigned charの場合は256)から減算する必要がありますか?例:符号付き数値に変換すると140は-116になります。しかし、20は20自体になります。ここで簡単なトリックはありますか?
Jon Wheelock


24

符号付きから符号なしへの変換は必ずしも符号付き値の表現をコピーまたは再解釈するだけではありませ。C標準(C99 6.3.1.3)の引用:

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

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

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

最近ではほとんど普遍的な2つの補数表現の場合、ルールはビットの再解釈に対応しています。ただし、他の表現(符号と絶対値または1の補数)の場合、C実装は同じ結果を準備する必要があります。つまり、変換はビットをコピーするだけではできません。たとえば、(unsigned)-1 == UINT_MAX、表現に関係なく。

一般に、Cでの変換は、表現ではなく値を操作するように定義されています。

元の質問に答えるには:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

iの値はunsigned intに変換され、が生成されUINT_MAX + 1 - 5678ます。次に、この値が符号なしの値1234に追加され、が生成されUINT_MAX + 1 - 4444ます。

(符号なしオーバーフローとは異なり、符号付きオーバーフローは未定義の動作を引き起こします。ラップアラウンドは一般的ですが、C標準では保証されていません-コンパイラーの最適化は、不当な仮定を行うコードに大混乱をもたらす可能性があります。)


5

参照すると、聖書

  • 追加操作により、intはunsigned intに変換されます。
  • 2の補数表現と同じサイズの型を仮定すると、ビットパターンは変化しません。
  • unsigned intからsigned intへの変換は実装に依存します。(しかし、最近はほとんどのプラットフォームで期待どおりに動作します。)
  • 異なるサイズの符号付きと符号なしを組み合わせる場合、ルールは少し複雑になります。

3

1つの符号なし変数と1つの符号付き変数(または任意のバイナリ演算)が追加されると、両方が暗黙的に符号なしに変換されます。この場合、結果は非常に大きくなります。

したがって、結果は巨大で間違っている可能性があるという意味では安全ですが、クラッシュすることはありません。


違います。6.3.1.8通常の算術変換 intとunsigned charを合計すると、後者はintに変換されます。2つのunsigned charを合計すると、それらはintに変換されます。
2501

3

符号付きから符号なしに変換する場合、2つの可能性があります。元々正であった数は同じ値のままです(または解釈されます)。もともと負だった数は、より大きな正数として解釈されます。


1

以前に回答されたように、問題なく署名済みと署名なしの間でキャストできます。符号付き整数のボーダーケースは-1(0xFFFFFFFF)です。そこから足し算と引き算を試してみてください。そうすれば、キャストバックして正確にすることができます。

ただし、前後にキャストする場合は、変数のタイプが明確になるように変数に名前を付けることを強くお勧めします。例:

int iValue, iResult;
unsigned int uValue, uResult;

ヒントなしで名前が付けられている場合、より重要な問題に気を取られて、どの変数がどのタイプであるかを忘れるのは非常に簡単です。符号なしにキャストして、それを配列インデックスとして使用したくない場合。


0

ここでどのような暗黙の変換が行われているか、

iは符号なし整数に変換されます。

そして、このコードはuとiのすべての値に対して安全ですか?

明確に定義されているという意味で安全です(https://stackoverflow.com/a/50632/5083516を参照)。

ルールは通常読みにくい標準で書かれていますが、基本的に、符号付き整数で使用された表現はすべて、符号なし整数には2の補数表現の数値が含まれます。

加算、減算、乗算はこれらの数値で正しく機能し、「実際の結果」を表す2の補数を含む別の符号なし整数になります。

除算してより大きな符号なし整数型にキャストすると、明確な結果が得られますが、これらの結果は「実際の結果」の2の補数表現にはなりません。

(安全です。この例の結果が巨大な正の数値にオーバーフローしても、それをintにキャストして実際の結果を得ることができるという意味です。)

符号付きから符号なしへの変換は標準で定義されていますが、逆は実装定義であり、gccとmsvcの両方が変換を定義して、符号なし整数に格納された2の補数を符号付き整数に変換するときに「実際の結果」が得られるようにします。符号付き整数に2の補数を使用しない不明瞭なシステムでのみ他の動作が見つかることを期待しています。

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx


-17

恐ろしい答えが豊富

オズグルオズシタク

符号付きから符号なし(またはその逆)にキャストしても、数値の内部表現は変わりません。変更点は、コンパイラーが符号ビットを解釈する方法です。

これは完全に間違っています。

マット・フレドリクソン

1つの符号なし変数と1つの符号付き変数(または任意のバイナリ演算)が追加されると、両方が暗黙的に符号なしに変換されます。この場合、結果は非常に大きくなります。

これも間違っています。符号なしのintは、符号なしの型のビットにパディングを行うことにより、精度が等しい場合、intに昇格される場合があります。

smh

追加操作により、intはunsigned intに変換されます。

違う。多分それはそうかもしれませんし、そうでないかもしれません。

unsigned intからsigned intへの変換は実装に依存します。(しかし、最近はほとんどのプラットフォームで期待どおりに動作します。)

違う。オーバーフローが発生するか、値が保持される場合、これは未定義の動作です。

匿名の

iの値はunsigned int ...に変換されます。

違う。unsigned intに対するintの精度に依存します。

テイラー・プライス

以前に回答されたように、問題なく署名済みと署名なしの間でキャストできます。

違う。符号付き整数の範囲外の値を格納しようとすると、未定義の動作が発生します。

ようやく質問に答えることができます。

intの精度がunsigned intと等しい場合、uはsigned intに昇格され、式(u + i)から値-4444が取得されます。ここで、uとiに他の値がある場合、オーバーフローや未定義の動作が発生する可能性がありますが、これらの正確な数値では-4444 [1]になります。この値の型はintです。しかし、その値をunsigned intに格納して、unsigned intにキャストしようとすると、結果の値は(UINT_MAX + 1)-4444になります。

unsigned intの精度がintの精度より大きい場合、signed intはunsigned intに昇格され、値(UINT_MAX + 1)-5678が生成されます。これは、他のunsigned int 1234に追加されます。uとiは式を{0..UINT_MAX}の範囲外にする他の値。値(UINT_MAX + 1)は、結果が{0..UINT_MAX)の範囲内に収まるまで加算または減算され、未定義の動作は発生しません。 。

精度とは?

整数には、パディングビット、符号ビット、および値ビットがあります。符号なし整数には、明らかに符号ビットはありません。符号なしcharには、パディングビットがないことがさらに保証されます。整数のビット値の数は、整数の精度です。

[ゴチャ]

埋め込みビットがある場合、整数sizeofマクロだけでは整数の精度を決定できません。また、バイトのサイズは、C99で定義されているオクテット(8ビット)である必要はありません。

[1]オーバーフローは2つのポイントのいずれかで発生する可能性があります。追加の前(プロモーション中)-intに収まらない大きさのunsigned intがある場合。unsigned intがintの範囲内であったとしても、加算後にオーバーフローが発生する可能性があります。加算後も結果はオーバーフローする可能性があります。


6
「署名されていない整数は整数に昇格される場合があります」。違います。タイプはすでにランク> = intであるため、整数の昇格は発生しません。6.3.1.1:「符号なし整数型のランクは、対応する符号付き整数型のランクと同等です(ある場合)。」そして6.3.1.8は:符号なし整数型を持つオペランドがランク大きいがある場合、「そうでない場合、またはそれに等しい他のオペランドの型のランクには、符号付き整数型を持つオペランドは符号なし整数とオペランドの型に変換されタイプ。" どちらも、通常の算術変換が適用intされるunsigned intときにに変換されることを保証します。
CBベイリー

1
6.3.1.8整数の昇格後にのみ発生します。冒頭の段落には、「そうでない場合、整数の昇格は両方のオペランドで実行されます。次に、昇格されたオペランドには次の規則が適用されます」と書かれています。したがって、プロモーションルール6.3.1.1を読んでください。「整数変換ランクがintおよびunsigned intのランクよりも小さいか等しいEQの整数型のオブジェクトまたは式」および「intが元のタイプの場合、値はintに変換されます。
Elite Mx 2010

1
6.3.1.1整数のプロモーションはないいくつかの整数型に変換するために使用使用intまたはunsigned intタイプの何か、それらの種類の一つにunsigned intまたはがint期待されているが。「又は等しい」変換ランクの列挙型が等しい可能にするTC2に加えint、またはunsigned intこれらのタイプのいずれかに変換します。説明されているプロモーションがunsigned intとの間で変換されることは意図されていませんでしたint。との間の一般的な型の決定はunsigned intintTC2以降でも6.3.1.8によって管理されます。
CBベイリー

19
他の人の間違った答えを批判しながら間違った答えを投稿することは、仕事を得るための良い戦略のように聞こえません... ;-)
R .. GitHub ICE HELPING ICE

6
傲慢さと組み合わされたこのレベルの誤りは面白すぎるので、私は削除することに投票しません
MM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.