ここでの回答のいくつかは、符号付きの値と符号なしの値の間の驚くべきプロモーションルールに言及していますが、それは混合に関連する問題のようですいますが、それは符号付き値と符号なし値の、符号付き変数が符号なしよりも優先される理由を必ずしも説明していませんシナリオを混合する外。
私の経験では、混合比較とプロモーションルール以外に、符号なしの値がバグマグネットである主な理由は次の2つです。
符号なしの値は、プログラミングで最も一般的な値であるゼロで不連続性があります
符号なし整数と符号付き整数はどちらも、最小値と最大値に不連続性があり、ラップアラウンド(符号なし)または未定義の動作(符号付き)を引き起こします。以下のためにunsigned
これらのポイントであるゼロとUINT_MAX
。以下のためにint
彼らはですINT_MIN
とINT_MAX
。代表値INT_MIN
とINT_MAX
4バイトを持つシステム上のint
値である-2^31
と2^31-1
、そのようなシステムにUINT_MAX
典型的です2^32-1
。
これにunsigned
当てはまらない主なバグ誘発の問題int
は、ゼロで不連続性があることです。もちろん、ゼロは、1,2,3のような他の小さな値とともに、プログラムで非常に一般的な値です。さまざまな構成で小さな値、特に1を加算および減算するのが一般的であり、unsigned
、それがたまたまゼロになると、大きな正の値とほぼ確実なバグが発生します。
コードが最後の0.5を除くインデックスによってベクトル内のすべての値を反復することを検討してください。
for (size_t i = 0; i < v.size() - 1; i++) {
これは、ある日空のベクトルを渡すまでは正常に機能します。ゼロ反復を行う代わりに、1を取得しますv.size() - 1 == a giant number
40億の反復を実行し、ほとんどバッファオーバーフローの脆弱性があります。
あなたはそれをこのように書く必要があります:
for (size_t i = 0; i + 1 < v.size(); i++) {
したがって、この場合は「修正」できますが、の符号なしの性質について慎重に検討する必要があります。 size_t
ます。定数の代わりに適用したい可変オフセットがあるため、上記の修正を適用できない場合があります。これは正または負の場合があります。したがって、比較のどちらの「側」に適用する必要があるかは、符号によって異なります。 -コードが非常に乱雑になりました。
ゼロまで反復しようとするコードにも同様の問題があります。のようなものwhile (index-- > 0)
は問題なく動作しますが、明らかに同等ですwhile (--index >= 0)
が符号なしの値で終了することはありません。右側がリテラルゼロの場合、コンパイラは警告を表示する場合がありますが、実行時に決定された値の場合は警告が表示されません。
対位法
符号付きの値にも2つの不連続性があると主張する人もいるかもしれませんが、なぜ符号なしを選択するのでしょうか。違いは、両方の不連続性がゼロから非常に(最大で)遠く離れていることです。私はこれを「オーバーフロー」の別の問題だと本当に考えています。符号付きと符号なしの両方の値が非常に大きな値でオーバーフローする可能性があります。多くの場合、値の可能な範囲の制約のためにオーバーフローは不可能であり、多くの64ビット値のオーバーフローは物理的に不可能である可能性があります。可能であっても、オーバーフロー関連のバグの可能性は、「ゼロ時」のバグと比較してごくわずかであることが多く、符号なしの値でもオーバーフローが発生します。したがって、unsignedは、両方の世界の最悪のものを組み合わせます。非常に大きなマグニチュード値でオーバーフローする可能性があり、ゼロで不連続になります。署名は前者のみです。
多くの人が、署名なしで「少し失う」と主張します。これはしばしば真実ですが、常にではありません(符号なしの値の違いを表す必要がある場合は、とにかくそのビットが失われます:とにかく多くの32ビットのものが2 GiBに制限されているか、奇妙な灰色の領域がありますファイルは4GiBにすることができますが、後半の2 GiBでは特定のAPIを使用できません)。
unsignedがあなたを少し買う場合でさえ:それはあなたをあまり買わない:あなたが20億以上の「もの」をサポートしなければならなかったなら、あなたはおそらくすぐに40億以上をサポートしなければならないでしょう。
論理的には、符号なしの値は符号付きの値のサブセットです
数学的には、符号なしの値(負でない整数)は符号付き整数のサブセットです(単に_integersと呼ばれます)。2。まだ署名された値は、自然のみに対する操作の飛び出し符号なしよう減算などの値、。符号なしの値は減算で閉じられないと言うかもしれません。同じことは符号付きの値には当てはまりません。
2つの符号なしインデックス間の「デルタ」をファイルに見つけたいですか?さて、あなたは正しい順序で減算を行う方が良いです、さもなければあなたは間違った答えを得るでしょう。もちろん、正しい順序を決定するためにランタイムチェックが必要になることがよくあります。符号なしの値を数値として扱う場合、(論理的に)符号付きの値がとにかく表示され続けることがよくあるので、符号付きから始めた方がよいでしょう。
対位法
上記の脚注(2)で述べたように、C ++の符号付き値は実際には同じサイズの符号なし値のサブセットではないため、符号なし値は符号付き値と同じ数の結果を表すことができます。
本当ですが、範囲はあまり役に立ちません。減算、0から2Nの範囲の符号なし数値、および-NからNの範囲の符号付き数値を検討してください。任意の減算は、どちらの場合も-2Nから2Nの範囲の結果になり、どちらのタイプの整数も表すことができます。その半分。-NからNのゼロを中心とする領域は、通常、0から2Nの範囲よりもはるかに有用です(実際のコードでより多くの実際の結果が含まれます)。均一以外の一般的な分布(log、zipfian、normalなど)を検討し、その分布からランダムに選択された値を減算することを検討します。[0、2N]よりも[-N、N]の方が多くの値になります(実際、結果の分布常にゼロを中心とします)。
64ビットは、符号付きの値を数値として使用する多くの理由でドアを閉めます
上記の議論はすでに32ビット値に対して説得力があると思いますが、「20億」は多くの人が超えることができる数であるため、異なるしきい値で符号付きと符号なしの両方に影響するオーバーフローのケースは32ビット値で発生します抽象的および物理的量(数十億ドル、数十億ナノ秒、数十億の要素を持つ配列)。したがって、符号なしの値の正の範囲が2倍になることで誰かが十分に確信している場合、オーバーフローが問題になり、符号なしをわずかに支持するという主張をすることができます。
特殊なドメイン以外では、64ビット値はこの懸念を大幅に取り除きます。符号付き64ビット値の上限範囲は9,223,372,036,854,775,807で、9兆を超えますます。それはたくさんのナノ秒(約292年の価値)であり、たくさんのお金です。また、どのコンピューターよりも大きなアレイであり、コヒーレントアドレス空間にRAMが長期間存在する可能性があります。それで、多分9千兆は(今のところ)誰にとっても十分ですか?
符号なしの値を使用する場合
スタイルガイドは、符号なしの数字の使用を禁止したり、必ずしも禁止したりしないことに注意してください。それはで終わります:
変数が負でないことを主張するためだけに符号なし型を使用しないでください。
確かに、符号なし変数には良い使用法があります。
Nビットの量を整数としてではなく、単に「ビットの袋」として扱いたい場合。たとえば、ビットマスクまたはビットマップ、あるいはN個のブール値などとして。この使用法は、変数の正確なサイズを知りたいことが多いため、のような固定幅タイプuint32_t
とuint64_t
密接に関連していることがよくあります。特定の変数がこの処理に値することをヒントを使用するのみとしてその上で動作することであるビット単位のようなオペレータ~
、|
、&
、^
、>>
など、としないような演算と+
、-
、*
、/
等
ビット単位の演算子の動作が明確に定義され、標準化されているため、ここでは符号なしが理想的です。符号付きの値には、シフト時の未定義および未指定の動作や、未指定の表現など、いくつかの問題があります。
実際にモジュラー演算が必要な場合。実際に2 ^ Nモジュラー演算が必要な場合があります。このような場合、「オーバーフロー」は機能であり、バグではありません。符号なしの値は、モジュラー演算を使用するように定義されているため、ここで必要なものを提供します。符号付きの値は、表現が指定されておらず、オーバーフローが定義されていないため、(簡単に、効率的に)使用することはできません。
0.5これを書いた後、これは私が見たことがなかったJarodの例とほぼ同じであることに気付きました-そして正当な理由で、それは良い例です!
1size_t
ここで話しているので、通常、32ビットシステムでは2 ^ 32-1、64ビットシステムでは2 ^ 64-1です。
2 C ++では、符号なしの値の上限に対応する符号付きの型よりも多くの値が含まれているため、これは正確には当てはまりませんが、符号なしの値を操作すると(論理的に)符号付きの値になる可能性があるという基本的な問題がありますが、対応する問題はありません符号付きの値を使用する(符号付きの値にはすでに符号なしの値が含まれているため)。
unsigned int x = 0; --x;
、どうx
なるか見てみましょう。制限チェックがないと、サイズが突然UBにつながる可能性のある予期しない値を取得する可能性があります。