署名されたintではなくunsignedintを使用すると、バグが発生する可能性が高くなりますか?どうして?


82

ではGoogleのC ++スタイルガイド、「符号なし整数」のトピックに、それがあることが示唆されました

歴史的な事故のため、C ++標準では、コンテナのサイズを表すために符号なし整数も使用しています。標準化団体の多くのメンバーは、これは間違いだと信じていますが、現時点で修正することは事実上不可能です。符号なし演算は単純な整数の動作をモデル化せず、代わりにモジュラー演算(オーバーフロー/アンダーフローでラップアラウンド)をモデル化する標準によって定義されるという事実は、重要なクラスのバグをコンパイラーが診断できないことを意味します。

モジュラー演算の何が問題になっていますか?それはunsignedintの期待される振る舞いではありませんか?

ガイドはどのようなバグ(重要なクラス)を参照していますか?バグがあふれていますか?

変数が負でないことを主張するためだけに符号なし型を使用しないでください。

unsignedintよりもsignedintを使用することを考えることができる理由の1つは、オーバーフローした場合(負の場合)、検出が容易になるためです。


4
やってみてunsigned int x = 0; --x;、どうxなるか見てみましょう。制限チェックがないと、サイズが突然UBにつながる可能性のある予期しない値を取得する可能性があります。
一部のプログラマー

33
少なくとも符号なしオーバーフローは明確に定義された動作をし、期待される結果を生成します。
user7860670 2018

35
無関係な(あなたの質問には関係ありますが、Googleスタイルガイドには関係ありません)ことに注意してください。少し検索すると、Googleスタイルガイドに対するいくつかの(時には正当な)批判が見つかります。それらを福音として受け取らないでください。
一部のプログラマー

18
一方、intオーバーフローとアンダーフローはUBです。ゼロ以下にint減少する状況よりも、ができない値を表現しようとする状況を経験する可能性は低くなりunsigned intますが、unsigned int算術の振る舞いに驚かされるような人々は、オーバーフローのチェックにint使用a < a + 1するなど、オーバーフロー関連のUBを引き起こすコードを記述します。
フランソワアンドリュー2018

12
符号なし整数がオーバーフローした場合、それは明確に定義されています。符号付き整数がオーバーフローした場合、それは未定義の動作です。私は明確に定義された動作を好みますが、コードがオーバーフローした値を処理できない場合は、両方で失われます。違いは次のとおりです。次のコードでは、署名されている場合はオーバーフロー操作で、署名されていない場合はすでに失われています。私が同意する唯一のポイントは、負の値が必要な場合、符号なし整数型は間違った選択です-明らかに。
このサイトには正直すぎる

回答:


71

ここでの回答のいくつかは、符号付きの値と符号なしの値の間の驚くべきプロモーションルールに言及していますが、それは混合に関連する問題のようですいますが、それは符号付き値と符号なし値の、符号付き変数が符号なしよりも優先される理由を必ずしも説明していませんシナリオを混合する外。

私の経験では、混合比較とプロモーションルール以外に、符号なしの値がバグマグネットである主な理由は次の2つです。

符号なしの値は、プログラミングで最も一般的な値であるゼロで不連続性があります

符号なし整数と符号付き整数はどちらも、最小値と最大値に不連続性があり、ラップアラウンド(符号なし)または未定義の動作(符号付き)を引き起こします。以下のためにunsignedこれらのポイントであるゼロUINT_MAX。以下のためにint彼らはですINT_MININT_MAX。代表値INT_MININT_MAX4バイトを持つシステム上のint値である-2^312^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++) { // do something }

これは、ある日空のベクトルを渡すまでは正常に機能します。ゼロ反復を行う代わりに、1を取得しますv.size() - 1 == a giant number 40億の反復を実行し、ほとんどバッファオーバーフローの脆弱性があります。

あなたはそれをこのように書く必要があります:

for (size_t i = 0; i + 1 < v.size(); i++) { // do something }

したがって、この場合は「修正」できますが、の符号なしの性質について慎重に検討する必要があります。 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_tuint64_t密接に関連していることがよくあります。特定の変数がこの処理に値することをヒントを使用するのみとしてその上で動作することであるビット単位のようなオペレータ~|&^>>など、としないような演算と+-*/

    ビット単位の演算子の動作が明確に定義され、標準化されているため、ここでは符号なしが理想的です。符号付きの値には、シフト時の未定義および未指定の動作や、未指定の表現など、いくつかの問題があります。

  • 実際にモジュラー演算が必要な場合。実際に2 ^ Nモジュラー演算が必要な場合があります。このような場合、「オーバーフロー」は機能であり、バグではありません。符号なしの値は、モジュラー演算を使用するように定義されているため、ここで必要なものを提供します。符号付きの値は、表現が指定されておらず、オーバーフローが定義されていないため、(簡単に、効率的に)使用することはできません。


0.5これを書いた後、これは私が見たことがなかったJarodの例とほぼ同じであることに気付きました-そして正当な理由で、それは良い例です!

1size_tここで話しているので、通常、32ビットシステムでは2 ^ 32-1、64ビットシステムでは2 ^ 64-1です。

2 C ++では、符号なしの値の上限に対応する符号付きの型よりも多くの値が含まれているため、これは正確には当てはまりませんが、符号なしの値を操作すると(論理的に)符号付きの値になる可能性があるという基本的な問題がありますが、対応する問題はありません符号付きの値を使用する(符号付きの値にはすでに符号なしの値が含まれているため)。


10
私はあなたが投稿したすべてに同意しますが、「64ビットはすべての人にとって十分なはずです」は確かに「640kはすべての人にとって十分なはずです」に近すぎるようです。
Andrew Henle 2018

6
@ Andrew-うん、私は自分の言葉を慎重に選んだ:)。
BeeOnRope 2018

4
「64ビットは符号なしの値でドアを閉めます」->同意しません。一部の整数計画タスクは、カウントの場合ではなく単純であり、負の値は必要ありませんが、2の累乗の幅が必要です。パスワード、暗号化、ビットグラフィックス、符号なし数学の利点。ここでの多くのアイデアは、コードが可能な場合に符号付き数学を使用できる理由を指摘していますが、符号なし型を役に立たなくしてそれらのドアを閉じるには非常に不十分です。
chux -復活モニカ

2
@ Deduplicator-ええ、それは多かれ少なかれネクタイのように見えるので、私はそれを省略しました。unsigned mod-2 ^ Nラップアラウンドの側では、少なくとも定義された動作があり、予期しない「最適化」が開始されることはありません。UBの側では、unsignedまたはsignedの演算中のオーバーフローは、おそらく圧倒的多数のバグです。場合によっては(mod演算を期待する少数の人を-ftrapv除いて)、コンパイラーは、すべての符号付きオーバーフローをキャッチできるが、すべての符号なしオーバーフローをキャッチできるようなオプションを提供します。パフォーマンスへの影響はそれほど悪くないので-ftrapv、いくつかのシナリオでコンパイルするのが合理的かもしれません。
BeeOnRope 2018

2
@BeeOnRopeThat's about the age of the universe measured in nanoseconds.私はそれを疑っています。宇宙は、またはである13.7*10^9 years古いについてです。intとして表すには、少なくとも。が必要です。約です。4.32*10^17 s4.32*10^26 ns4.32*10^2690 bits9,223,372,036,854,775,807 ns292.5 years
オシリス

37

述べたように、混合するunsignedsigned、予期しない動作が発生する可能性があります(明確に定義されている場合でも)。

最後の5つを除いて、ベクトルのすべての要素を反復処理したい場合、誤って次のように記述する可能性があります。

for (int i = 0; i < v.size() - 5; ++i) { foo(v[i]); } // Incorrect
// for (int i = 0; i + 5 < v.size(); ++i) { foo(v[i]); } // Correct

仮定v.size() < 5として、その後、v.size()であるunsigneds.size() - 5非常に多くなり、そのためi < v.size() - 5であろうtrue値より予想範囲に対してi。そして、UBはすぐに発生します(一度は範囲外のアクセスi >= v.size()

v.size()符号付きの値を返す場合はs.size() - 5負になり、上記の場合、条件はすぐにfalseになります。

他の側では、インデックスは、の間でなければならない[0; v.size()[のでunsigned、理にかなっています。符号付きには、負の符号付き数値の右シフトに対するオーバーフローまたは実装定義の動作を伴うUBとしての独自の問題もありますが、反復のバグの原因はそれほど頻繁ではありません。


2
私自身はできる限り符号付き数値を使用していますが、この例は十分に強力ではないと思います。符号なしの数値を長い間使用している人は、確かにこのイディオムを知っています。代わりにi<size()-X、を書く必要がありますi+X<size()。確かに、それは覚えておくべきことですが、私の意見では、慣れるのはそれほど難しいことではありません。
geza 2018

8
あなたが言っていることは、基本的に、言語と型間の強制規則を知らなければならないということです。質問のとおり、符号付きと符号なしのどちらを使用しても、これがどのように変化するかわかりません。負の値が必要ない場合は、signedを使用することをお勧めします。@gezaに同意し、必要な場合にのみ署名を使用ます。これはグーグルガイドをせいぜい疑わしいものにします。イモそれは悪いアドバイスです。
このサイトには正直すぎる

2
@toohonestforthissite要点は、ルールが難解で、沈黙していて、バグの主な原因であるということです。算術演算に排他的に署名された型を使用すると、問題が軽減されます。ところで、正の値を強制する目的で符号なし型を使用することは、それらにとって最悪の悪用の1つです。
通行人2018年

2
ありがたいことに、最新のコンパイラとIDEは、式に符号付きと符号なしの数値を混在させると警告を出します。
Alexey B.

5
@PasserBy:それらを難解と呼ぶ場合は、整数のプロモーションと、署名されたタイプの難解なオーバーフローのためのUBも追加する必要があります。そして、非常に一般的なsizeof演算子はとにかく符号なしを返すので、それらについて知る必要があります。つまり、言語の詳細を学びたくない場合は、CまたはC ++を使用しないでください。グーグルが行くことを促進することを考えると、多分それはまさに彼らの目標です。「邪悪になるな」の時代は過ぎ去りました…
このサイトには正直すぎます

20

エラーの最も問題のある例の1つは、符号付きの値と符号なしの値を混合した場合です。

#include <iostream>
int main()  {
    auto qualifier = -1 < 1u ? "makes" : "does not make";
    std::cout << "The world " << qualifier << " sense" << std::endl;
}

出力:

世界は意味をなさない

些細なアプリケーションがない限り、符号付きの値と符号なしの値が危険に混ざり合ってしまう(ランタイムエラーが発生する)か、警告を上げてコンパイル時エラーにすると、多くの結果が発生することは避けられません。コード内のstatic_casts。そのため、数学または論理比較の型には符号付き整数を厳密に使用するのが最善です。ビットを表すビットマスクとタイプには、unsignedのみを使用してください。

数値の値の予想されるドメインに基づいて符号なしの型をモデル化することは悪い考えです。ほとんどの数値は20億よりも0に近いため、符号なしの型では、多くの値が有効な範囲の端に近くなります。さらに悪いことに、最終的な値は既知の正の範囲にある可能性がありますが、式を評価している間、中間値がアンダーフローする可能性があり、中間形式で使用される場合は非常に間違った値になる可能性があります。最後に、値が常に正であると予想される場合でも、負になる可能性のある他の変数と相互作用しないことを意味するわけではないため、符号付き型と符号なし型が混在するという強制的な状況になります。最悪の場所。


8
数値の値の予想されるドメインに基づいて符号なしの型をモデル化することは悪い考えです*暗黙の変換を警告として扱わず、怠惰すぎて適切な型キャストを使用できない場合*。予想される有効な型に基づいて型をモデル化する組み込み型のC / C ++ではなく、値は完全に合理的です。
villasv

1
@ user7586189無効なデータをインスタンス化できないようにすることをお勧めします。そのため、サイズに正のみの変数を設定することは完全に合理的です。ただし、C / C ++組み込み型を微調整して、この回答のようなデフォルトの不正なキャストを許可しないようにすることはできません。その有効性は、最終的に他の誰かの責任になります。キャストがより厳密な言語を使用している場合(組み込み間でも)、期待されるドメインモデリングはかなり良い考えです。
villasv

1
注、私がやった言及は警告を上げるとエラーにそれらを設定することではなく、誰もがありません。@villasvは、モデリング値に関するあなたの声明にまだ同意しません。unsignedを選択することにより、それが何であるかについて多くの先見性持たずに、接触する可能性のある他のすべての値を暗黙的にモデル化することにもなります。そして、ほぼ間違いなくそれを間違えています。
Chris Uzdavinis 2018

1
ドメインを念頭に置いてモデリングするのは良いことです。unsignedを使用してドメインをモデル化することはできません。(符号付きと符号なしは、他の方法で実行できない場合を除いて、値の範囲ではなく、使用法のタイプに基づいて選択する必要があります。)
Chris Uzdavinis 2018

2
コードベースに符号付きの値と符号なしの値が混在している場合、警告を表示してエラーにプロモートすると、変換を明示的にするためにコードにstatic_castsが散らばってしまいます(計算を行う必要があるため)。エラーが発生しやすく、操作が難しく、読みにくいです。
Chris Uzdavinis 2018

11

unsigned intを使用すると、signed intを使用するよりもバグが発生する可能性が高いのはなぜですか?

符号なしの型を使用する、特定のクラスのタスクで符号付きの型を使用するよりもバグが発生する可能性が高くなりません。

仕事に適したツールを使用してください。

モジュラー演算の何が問題になっていますか?それはunsignedintの期待される振る舞いではありませんか?
unsigned intを使用すると、signed intを使用するよりもバグが発生する可能性が高いのはなぜですか?

タスクがよく一致している場合:何も問題はありません。いいえ、可能性は高くありません。

セキュリティ、暗号化、および認証アルゴリズムは、署名されていないモジュラー数学に依存しています。

圧縮/解凍アルゴリズムやさまざまなグラフィック形式にもメリットがあり、符号なしの計算ではバグが少なくなります。

ビット単位の演算子とシフトが使用されるときはいつでも、符号なし演算が次の符号拡張の問題に混乱することはありません。付き数学の。


符号付き整数数学は、コーディングの学習者を含むすべての人がすぐに理解できる直感的なルックアンドフィールを備えています。C / C ++は元々ターゲットにされていなかったし、現在はイントロ言語であるべきではありません。オーバーフローに関するセーフティネットを使用する迅速なコーディングには、他の言語の方が適しています。リーンファストコードの場合、Cは、コーダーが自分が何をしているかを知っている(経験がある)と想定します。

落とし穴署名数学は本日、ユビキタス、32ビットであるintので、多くの問題で範囲チェックせずに一般的なタスクのためにも十分な広さであること。これは、オーバーフローがコーディングされていない自己満足につながります。代わりに、<とfor (int i=0; i < n; i++) int len = strlen(s);見なされ、文字列が長すぎることnはないため、OKと見なされINT_MAXます。最初のケースではフルレンジで保護されるか、を使用size_tするunsignedlong long、2番目のケースでさえも保護されます。

16ビットintと32ビットを含む時代に開発されたC / C ++と、符号なし16ビットがsize_t提供する余分なビットは重要でした。注意がそれもオーバーフローの問題に関して必要でしたintunsigned

16ビット以外のint/unsignedプラットフォームでのGoogleの32ビット(またはそれ以上)のアプリケーションではint、十分な範囲があるため、+ /-オーバーフローに注意を払う必要がありません。このようなアプリケーションを奨励するためにこれは理にかなってintオーバーunsigned。しかし、int数学は十分に保護されていません。

狭い16ビットのint/unsigned懸念は、今日、一部の組み込みアプリケーションに当てはまります。

Googleのガイドラインは、今日作成するコードに適しています。これは、C / C ++コードのより広い範囲の範囲に対する決定的なガイドラインではありません。


unsignedintよりもsignedintを使用することを考えることができる理由の1つは、オーバーフローした場合(負の場合)、検出が容易になるためです。

C / C ++では、signed int mathオーバーフローは未定義の動作であるため、unsignedmathの定義済みの動作よりも検出が容易ではありません。


@クリスUzdavinisはよくコメント、混合符号付きおよび符号なしの最高の全て(特に初心者)によって回避し、必要なときに、さもなければ注意深く符号化されます。


2
あなたintは、が「実際の」整数の振る舞いもモデル化していないという良い点を述べています。オーバーフロー時の未定義の振る舞いは、数学者が整数を考える方法ではありません。抽象的な整数で「オーバーフロー」する可能性はありません。しかし、これらはマシンストレージユニットであり、数学者の数ではありません。
tchrist 2018

1
@tchrist:オーバーフロー時の符号なしの動作は、数学者が整数合同mod(type_MAX + 1)の抽象代数環についてどのように考えるかです。
スーパーキャット2018

gccを使用している場合、signed intオーバーフローは(を使用して-ftrapv)簡単に検出できますが、符号なしの「オーバーフロー」は検出が困難です。
anatolyg 2018

5

私はグーグルのスタイルガイド、別名ヒッチハイカーのガイドで、ずっと前に会社に入った悪いプログラマーからの非常識な指令についての経験があります。この特定のガイドラインは、その本にある数十のくだらないルールの一例にすぎません。

エラーは、符号なし型で算術演算を実行しようとした場合(上記のChris Uzdavinisの例を参照)、つまり数値として使用した場合にのみ発生します。符号なしタイプは、数値を格納するために使用することを意図したものではなく、コンテナのサイズなど、負になることのないカウントを格納することを目的としています。これらは、その目的で使用でき、使用する必要があります。

算術型(符号付き整数など)を使用してコンテナーサイズを格納するという考えはばかげています。リストのサイズもdoubleを使用して保存しますか?Googleには、算術型を使用してコンテナサイズを保存し、他の人にも同じことを要求する人がいるということは、会社について何かを物語っています。そのような指示について私が気づいたことの1つは、彼らが愚かであるほど、常識のある人々が規則を無視するので、彼らは厳格な「やる気を起こさせる」規則である必要があるということです。


私はあなたのドリフトを取得しますが、unsigned型がカウントのみを保持でき、算術で使用できない場合、作成された包括的なステートメントはビット単位の演算を実質的に排除します。したがって、「悪いプログラマーからの非常識な指令」の部分はより理にかなっています。
デビッドC.ランキン

@ DavidC.Rankin「包括的な」ステートメントと見なさないでください。明らかに、符号なし整数には複数の正当な用途があります(ビット単位の値の格納など)。
タイラーダーデン2018

はい、はい-私はしませんでした、それで私は「私はあなたのドリフトを得る」と言いました。
デビッドC.ランキン

1
カウントは、インデックスなど、算術演算が行われているものと比較されることがよくあります。Cが符号付きと符号なしの数値を含む比較を処理する方法は、多くの奇妙な癖につながる可能性があります。カウントの最上位値が符号なしに収まるが、対応する符号付きタイプには収まらない状況(int16ビットだった時代には一般的でしたが、今日でははるかに少ない)を除いて、数値のように動作するカウントを使用することをお勧めします。
スーパーキャット2018

1
「エラーは、符号なし型で算術演算を実行しようとした場合にのみ発生します」-これは常に発生します。「算術型(符号付き整数など)を使用してコンテナーサイズを格納するという考えはばかげています」-そうではなく、C ++委員会はsize_tを使用することは歴史的な間違いであると考えています。理由?暗黙の変換。
アティラネベス

1

符号なし型を使用して非負の値を表す...

  • 他の回答が詳細に示して説明しているように、符号付きおよび符号なしの値を使用すると、型の昇格に関連するバグが発生する可能性高くなりますが、
  • です 望ましくない/許可されていない値を表すことができるドメインを持つタイプの選択に関連するバグを引き起こす可能性低くなります。一部の場所では、値がドメイン内にあると想定し、他の値が何らかの形で侵入すると、予期しない潜在的に危険な動作が発生する可能性があります。

Googleコーディングガイドラインは、最初の種類の考慮事項に重点を置いています。C ++コアガイドラインなどの他のガイドラインセットでは、2番目のポイントにさらに重点が置かれています。たとえば、コアガイドラインI.12について考えてみます。

I.12:nullであってはならないポインタを次のように宣言します。 not_null

理由

nullptrエラーの逆参照を回避するため。の冗長なチェックを回避してパフォーマンスを向上させるためnullptr

int length(const char* p);            // it is not clear whether length(nullptr) is valid
length(nullptr);                      // OK?
int length(not_null<const char*> p);  // better: we can assume that p cannot be nullptr
int length(const char* p);            // we must assume that p can be nullptr

ソースで意図を述べることにより、実装者とツールは、静的分析を通じてエラーのいくつかのクラスを見つけるなどのより良い診断を提供し、ブランチやヌルテストの削除などの最適化を実行できます。

もちろん、non_negative両方のカテゴリのエラーを回避する整数のラッパーについて議論することもできますが、それには独自の問題があります...


0

グーグルステートメントは、コンテナのサイズタイプとしてunsignedを使用することについてですです。対照的に、質問はより一般的であるように見えます。あなたが読んでいる間、それを覚えておいてください。

これまでのほとんどの回答はグーグルステートメントに反応しましたが、より大きな質問には反応しなかったので、私は負のコンテナサイズについての回答を開始し、その後、署名されていないことが良いことを誰かに説得しようとします(願わくば、私は知っています...)。

署名されたコンテナサイズ

誰かがバグをコーディングしたとしましょう。その結果、コンテナインデックスが負になります。その結果、未定義の動作または例外/アクセス違反が発生します。インデックスタイプが署名されていないときに未定義の動作や例外/アクセス違反を取得するよりも本当に良いですか?違うと思う。

さて、数学とこの文脈で「自然な」ものについて話すのが好きなクラスの人々がいます。本質的に> = 0である何かを記述するために、負の数の整数型はどのように自然になりますか?負のサイズの配列を多く使用していますか?IMHO、特に数学に傾倒している人々は、このセマンティクスの不一致(サイズ/インデックスタイプは負の可能性があると言っていますが、負のサイズの配列は想像しにくい)を苛立たせます。

したがって、この問題に残っている唯一の質問は、グーグルのコメントで述べられているように、コンパイラが実際にそのようなバグを見つけるのを積極的に支援できるかどうかです。そして、アンダーフローで保護された符号なし整数である代替案よりもさらに優れています(x86-64アセンブリおよびおそらく他のアーキテクチャにはそれを実現する手段があり、C / C ++のみがそれらの手段を使用しません)。私が理解できる唯一の方法は、コンパイラが実行時チェック(if (index < 0) throwOrWhatever)を自動的に追加した場合、またはコンパイル時アクションの場合に、「この配列アクセスのインデックスが負になる可能性がある」という誤検知の可能性のある警告/エラーを多数生成する場合です。疑問があります。これは役に立ちます。

また、実際に配列/コンテナインデックスのランタイムチェックを作成する人は、符号付き整数を処理する方が手間がかかります。書く代わりに、if (index < container.size()) { ... }あなたは今書く必要があります:if (index >= 0 && index < container.size()) { ... }。私には強制労働のように見えますが、改善のようではありません...

符号なしの型のない言語は吸う...

はい、これはJavaでの刺し傷です。今、私は組み込みプログラミングのバックグラウンドを持っており、二項演算(and、or、xor、...)とビット単位の値の構成が文字通りパンとバターであ​​るフィールドバスで多くの作業を行いました。私たちの製品の1つとして、私たち(またはむしろ顧客)はJavaポートを望んでいました...そして私は移植を行った幸運な非常に有能な人の反対側に座っていました(私は拒否しました...)。彼は落ち着きを保とうとしました...そして沈黙の中で苦しみました...しかし痛みはそこにありました、彼は署名された整数値を絶えず扱った数日後に呪いを止めることができませんでした、それは署名されるべきではありません...これらのシナリオは苦痛であり、私個人的には、Javaは符号付き整数を省略し、符号なしを提供した方がよかったと思います...少なくとも、符号の拡張子などを気にする必要はありません...

これらは私の5セントです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.