符号なし整数に関するベストプラクティスは何ですか?


43

私はどこでも符号なしintを使用していますが、そうすべきかどうかはわかりません。これは、データベースの主キーID列からカウンタなどになります。数値が負の値にならないようにする場合は、常にunsigned intを使用します。

しかし、他のコードでは、他の誰もこれを行っていないようです。私が見落としている重要なものはありますか?

編集:この質問以来、私はCでは、C ++のように例外をスローするのではなく、エラーに対して負の値を返すことが一般的であることに気付きました。


26
for(unsigned int n = 10; n >= 0; n --)(無限ループ)に注意してください
クリスバートブラウン

3
CおよびC ++では、符号なしintのオーバーフロー動作が厳密に定義されています(モジュロ2 ^ n)。署名付き整数はサポートしていません。オプティマイザーはその未定義のオーバーフロー動作をますます悪用し、場合によっては驚くべき結果をもたらします。
Steve314

2
良い質問!私もかつて範囲を制限するためにuintsを使用したいと思いましたが、リスク/不便さは利益/不便さを上回っていることを発見しました。あなたが言ったように、ほとんどのライブラリはuintが行う通常のintを受け入れます。これにより、作業が困難になりますが、質問もあります。それだけの価値があるのでしょうか。実際には(物事を馬鹿げたやり方で進めないと仮定して)、正の値が期待される場所に-218の値が入ることはめったにありません。その-218はどこかから来たに違いありませんか?そしてその起源をたどることができます。めったに起こりません。アサーション、例外、コードコントラクトを利用して支援してください。
ジョブ

@William Ting:これがC / C ++のみの場合、質問に適切なタグを追加する必要があります。
CesarGon

2
@クリス:現実には無限ループの問題はどれほど重要ですか?つまり、もしそれがリリースに進むなら、コードは明らかにテストされていません。このエラーを最初にデバッグするのに数時間かかる場合でも、2回目は、コードがループを停止しないときに最初に何を探すべきかを知っておく必要があります。
安全な

回答:


28

私が見落としている重要なものはありますか?

計算に符号付き型と符号なし型の両方と異なるサイズが含まれる場合、型の昇格の規則が複雑になり、予期ない動作が発生する可能性があります

これがJavaが符号なしint型を省略した主な理由だと思います。


3
別の解決策は、必要に応じて手動で数字をキャストすることを要求することです。これはGoが行うように見えることです(私はほんの少しだけ遊んでいますが)、Javaのアプローチよりも好きです。
ティコンジェルビス

2
Javaが64ビットの符号なしの型を含まない正当な理由であり、おそらく32ビットの符号なしの型を含まない妥当な理由です(ただし、符号付きおよび符号なしの32ビット値を追加するセマンティクスは難しくありませんが、そのような操作は、単に64ビットの署名された結果を生成するはずです。intただし、このような困難はありませんが、符号なしの型は小さくなります(計算がに進むためint)。符号なしバイト型の欠如について、私は何も言うことがありません。
supercat 14

17

Michaelには有効なポイントがあると思いますが、IMOが常に(特にfor (int i = 0; i < max, i++)intを使用する理由は、そのように学んだからです。「プログラミング学習方法」の本のすべての例がループで使用さintれているfor場合、その実践に疑問を呈する人はほとんどいません。

もう1つの理由は、intよりも25%短いことuintです。


2
教育問題に同意します。ほとんどの人は自分が読んだものに疑問を持たないようです。それが本の中にあるなら、それは間違いではないでしょう?
マチューM.

1
++ループインデックスが反復子またはその他の非基本型(またはコンパイラーが本当に高密度)である場合、その特定の動作はほとんど必要なく、コピーを無意味に混乱させることさえあるという事実にもかかわらず、増分時に誰もが後置記号を使用する理由です。
underscore_d

「for(uint i = 10; i> = 0; --i)」のようなことはしないでください。ループ変数にintのみを使用すると、この可能性を回避できます。
デビッドソーンリー


8

署名されたタイプと署名されていないタイプを混在させると、苦痛の世界に陥ります。そして、すべての符号なし型を使用することはできません。なぜなら、負の数値を含む有効な範囲を持つものに遭遇するか、エラーを示す値が必要であり、-1が最も自然だからです。そのため、最終的な結果は、多くのプログラマーがすべての符号付き整数型を使用することです。


1
たぶん、同じ変数に有効な値とエラー表示を混在させないで、このために別々の変数を使用する方が良いでしょう。確かに、C標準ライブラリはここで良い例を設定していません。
セキュリティ保護

7

私にとって、タイプとはコミュニケーションに関するものです。明示的に符号なし整数を使用することにより、符号付きの値は有効な値ではないことを教えてくれます。これにより、コードを読み取るときに変数名に加えていくつかの情報を追加できます。理想的には、匿名ではないタイプの方が詳細を教えてくれますが、あらゆる場所でintを使用した場合よりも多くの情報が得られます。

残念ながら、誰もが自分のコードが伝えることについてあまり意識していないわけではありません。それはおそらく、値が少なくとも符号なしであっても、どこにでもintを見る理由です。


4
ただし、1か月の値を1〜12に制限したい場合があります。別のタイプを使用しますか?1か月はどうですか?一部の言語では、実際にそのような値を制限できます。.Net / C#などのその他は、コードコントラクトを提供します。確かに、負でない整数はかなり頻繁に発生しますが、このタイプをサポートするほとんどの言語は、さらなる制限をサポートしていません。したがって、uintとエラーチェックを組み合わせて使用​​する必要がありますか、それともエラーチェックを介してすべてを実行する必要がありますか?ほとんどのライブラリーは、使用するのが理にかなっている場所でuintを要求しないため、使用してキャストするのは不便です。
ジョブ

@Jobあなたはあなたの月に何らかの種類のコンパイラ/インタプリタによって強制された制限を使うべきだと言うでしょう。それはあなたに設定するためのいくつかの決まり文句を与えるかもしれませんが、将来のためにあなたはエラーを防ぎ、あなたが期待しているものをより明確に伝える強制された制限を持っています。エラーを防止し、コミュニケーションを容易にすることは、実装中の不便さよりもはるかに重要です。
daramarak

1
「月の値を1〜12のみに制限したい場合があります月などの有限の値セットがある場合は、生の整数ではなく列挙型を使用する必要があります。
ジョシュキャスウェル

6

unsigned intはC ++で主に配列インデックスに使用し、0から始まるカウンターに使用します。「この変数を負にすることはできません」と明示的に言ってよいと思います。


14
おそらくc ++でこれにsize_tを使用する必要があります
-JohnB

2
気にしないでください。
quant_dev

3

実際に符号付きintの制限に近づいたり、超えたりする可能性のある整数を扱う場合は、これに注意する必要があります。32ビット整数の正の最大値は2,147,483,647であるため、a)負にならず、b)2,147,483,648に達する可能性があることがわかっている場合は、unsigned intを使用する必要があります。データベースキーとカウンターを含むほとんどの場合、これらの種類の数値にアプローチすることはないため、数値に符号ビットを使用するのか、符号を示すのに悩む必要はありません。

私は言うだろう:あなたがあなたが署名されていないintを必要とすることを知っていない限りintを使用する。


2
最大値に達する可能性のある値を操作する場合は、符号に関係なく整数オーバーフローの操作のチェックを開始する必要があります。通常、これらのチェックは、未定義および実装定義の動作なしで、ほとんどの操作で結果が明確に定義されているため、符号なしの型の方が簡単です。
セキュリティ保護

3

シンプルさと信頼性のトレードオフです。コンパイル時にキャッチできるバグが多いほど、ソフトウェアの信頼性は高くなります。さまざまな人々や組織が、そのスペクトルに沿ってさまざまなポイントにいます。

Adaで高信頼性プログラミングを行う場合、フィート単位の距離とメートル単位の距離などの変数に異なるタイプを使用し、誤って一方に割り当てた場合にコンパイラがフラグを立てます。これは、誘導ミサイルのプログラミングには最適ですが、Webフォームを検証している場合はやり過ぎです(しゃれを意図しています)。要件に適合する限り、どちらの方法でも必ずしも問題はありません。


2

私はジョエル・イーサトンの推論に同意する傾向があるが、反対の結論に達する。数字が符号付き型の限界に近づきそうにないことを知っていたとしても、負の数が発生しないことを知っていれば、型の符号付きバリアントを使用する理由はほとんどありません。

いくつかの選択したインスタンスで、SQL Serverテーブルで(32 BIGINTビット整数)ではなく(64ビット整数)を使用しているのと同じ理由でINTEGER。妥当な時間内にデータが32ビットの制限に達する可能性はごくわずかですが、発生した場合、状況によっては非常に壊滅的な結果になる可能性があります。言語間でタイプを適切にマッピングするようにしてください。そうしないと、本当に面白い奇妙な結果になってしまいます...

ただし、データベースの主キー値など、署名されたものまたは署名されていないものについては、実際には問題ではありません。それは識別子であり、それ以上のものではありません。これらの場合、署名の正確な選択よりもおそらく一貫性が重要です。そうしないと、明らかなパターンのない、署名された外部キー列と署名されていない他の列ができあがります。


SAPシステムから抽出したデータを使用している場合、IDフィールド(CustomerNumber、ArticleNumberなど)にはBIGINTを強くお勧めします。IDとして英数字の文字列を使用しない限り、つまり... ため息
-Treb

1

スペースに制約のあるデータストレージコンテキストとデータ交換コンテキストの外側では、一般に署名された型を使用することをお勧めします。32ビットの符号付き整数は小さすぎるが、今日では32ビットの符号なしの値で十分な場合、ほとんどの場合、32ビットの符号なしの値が十分に大きくなる前に長くなりません。

符号なしの型を使用する主な時間は、複数の値を大きな値に組み立てる(4バイトを32ビット数に変換する)か、大きな値を小さな値に分解する(例:32ビット数を4バイトとして格納する) )、または定期的に「ロールオーバー」することが予想される数量があり、それに対処する必要がある場合(住宅用ユーティリティメーターの場合、ほとんどの場合、読み取り間でロールオーバーしないように十分な桁がある) 1年に3回読まれても、メーターの耐用年数内でロールオーバーしないようにするには十分ではありません)。符号なしの型は、しばしばセマンティクスが必要な場合にのみ使用されるほど十分な「奇妙さ」を持っています。


1
「[...]一般に署名されたタイプを使用することをお勧めします。」うーん、あなたは署名された型の利点について言及するのを忘れて、いつ署名されていない型を使うべきかについてのリストだけを与えました。「すごみ」?ほとんどの未署名の操作には明確に定義された動作と結果がありますが、署名された型(オーバーフロー、ビットシフトなど)を使用する場合は未定義および実装定義の動作を入力します。ここに「奇妙さ」の奇妙な定義があります。
セキュリティ保護

1
@Secure:私が参照する「奇妙さ」は、比較演算子のセマンティクスに関係しています。特に、符号付き型と符号なし型が混在する操作ではそうです。オーバーフローするのに十分な大きさの値を使用する場合、符号付き型の動作は未定義であることは正しいですが、符号なし型の動作は、比較的小さな数を扱う場合でも驚くべきことです。たとえば、(-3)+(1u)は-1より大きいです。また、数値に適用される通常の数学的連想関係は、符号なしには適用されません。たとえば、(ab)> cは(ac)> bを意味しません。
-supercat

1
@Secure:「大きな」符号付き数値の連想動作に常に依存できるわけではないのは事実ですが、符号付き整数のドメインに対して「小さな」数値を処理する場合、動作は期待どおりに機能します。対照的に、上記の非関連付けは、符号なしの値「2 3 1」では問題があります。ちなみに、範囲外で使用した場合、署名された動作が未定義の動作をするという事実により、ネイティブワードサイズよりも小さい値を使用する場合、一部のプラットフォームでコード生成が改善されます。
-supercat

1
そもそも、これらのコメントが、理由を示すことなく推奨や「名前の呼びかけ」の代わりにあなたの答えにあったなら、私はコメントしなかっただろう。;)私はまだここで「奇妙さ」に同意していませんが、それは単に型の定義です。もちろん、特定のジョブに適切なツールを使用し、ツールを知っています。+/-リレーションが必要な場合、符号なしの型は間違ったツールです。size_t署名されておらず署名されている理由がありptrdiff_tます。
確保

1
@Secure:ビットシーケンスを表現したい場合は、符号なしの型が最適です。私たちはそこに同意すると思います。また、一部の小さなマイクロでは、符号なしの型の方が数値量に対してより効率的です。また、デルタは数値を表すが実際の値は表さない場合(TCPシーケンス番号など)にも役立ちます。一方、符号なしの値を減算するときはいつでも、数値が小さい場合でもコーナーケースを心配する必要があります。符号付きの値を使用したこのような数学は、数値が大きい場合にのみコーナーケースを示します。
supercat

1

符号なしintを使用して、コードとその意図をより明確にします。符号付き型と符号なし型の両方で算術を行うときに予期しない暗黙的な変換を防ぐために行うことの1つは、符号なし変数に符号なしshort(通常2バイト)を使用することです。これはいくつかの理由で効果的です:

  • 符号なしのshort変数とリテラル(int型)またはint型の変数を使用して算術を行う場合、intのランクは常にshortよりも高いため、式を評価する前に符号なし変数は常にintに昇格されます。 。これにより、式の結果が符号付きintに適合すると仮定して、符号付きおよび符号なしの型で算術を行う予期しない動作が回避されます。
  • ほとんどの場合、使用している符号なし変数は、符号なし2バイトショートの最大値(65,535)を超えません。

一般的な原則は、署名された型への昇格を保証するために、署名されていない変数の型は、署名された変数の型よりも低いランクを持つ必要があるということです。そうすれば、予期しないオーバーフロー動作は発生しません。明らかにこれを常に保証することはできませんが、(ほとんどの場合)これを保証することは可能です。

たとえば、最近、次のようなforループがいくつかありました。

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

リテラル '2'はint型です。iがunsigned shortではなくunsigned intであった場合、部分式(i-2)では、2がunsigned intに昇格されます(unsigned intはsigned intよりも優先度が高いため)。i = 0の場合、部分式は(0u-2u)=オーバーフローによる何らかの大きな値に等しくなります。i = 1の場合も同じ考えです。ただし、iは符号なしのshortであるため、リテラル「2」と同じ型に昇格されます。これは符号付きintであり、すべて正常に機能します。

安全性を高めるために:実装しているアーキテクチャがintを2バイトにするまれなケースでは、これにより、unsigned short変数が適合しない場合、算術式の両方のオペランドがunsigned intに昇格する可能性があります符号付き2バイト整数に変換します。後者の最大値は32,767 <65,535です。(詳細については、https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsignedを参照してください)。これを防ぐには、次のようにプログラムにstatic_assertを追加するだけです。

static_assert(sizeof(int) == 4, "int must be 4 bytes");

また、intが2バイトのアーキテクチャではコンパイルされません。

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