CおよびC ++で符号なし整数を使用する


23

長い間私を困惑させる非常に簡単な質問があります。私はネットワークとデータベースを扱っているので、扱うデータの多くは32ビットと64ビットのカウンター(符号なし)、32ビットと64ビットの識別IDです(また、符号の意味のあるマッピングはありません)。私は、負の数として表現される可能性のある実際の単語の問題を実際に扱うことは決してありません。

私と私の同僚は、これらの問題のためにuint32_t、通常、符号なしの型uint64_tを使用します。頻繁に発生するため、配列インデックスや他の一般的な整数の使用にも使用します。

同時に、私が読んでいるさまざまなコーディングガイド(Googleなど)は、符号なし整数型の使用を推奨しておらず、JavaとScalaのどちらにも符号なし整数型がないことを知っています。

ですから、私たちの環境で署名された値を使用するのは非常に不便であると同時に、コーディングガイドがまさにこれを行うことを主張する正しいことを理解できませんでした。


回答:


31

これについては2つの考え方があり、どちらも同意しません。

最初の例では、配列インデックスなど、本質的に符号なしの概念がいくつかあると主張しています。エラーが発生する可能性があるため、符号付きの数字を使用することは意味がありません。また、不必要な制限を課す可能性があります-符号付き32ビットインデックスを使用する配列は20億エントリにしかアクセスできませんが、符号なし32ビット番号に切り替えると40億エントリが許可されます。

2番目は、符号なしの数値を使用するプログラムでは、遅かれ早かれ、符号付きと符号なしの混合演算を行うことになります。これにより、奇妙で予期しない結果が生じる可能性があります。大きな符号なしの値を符号付きにキャストすると負の数値が得られ、逆に負の数を符号なしにキャストすると大きな正の数値が得られます。これはエラーの大きな原因になる可能性があります。


8
符号付きと符号なしの混合算術の問題は、コンパイラによって検出されます。ビルドに警告を付けないでください(十分に高い警告レベルで)。その上、入力する方intが短いです:)
rucamzu 14年

7
告白:私は2番目の考え方に賛同していますが、符号なしの型に関する考慮事項は理解していintますが、配列インデックスの99.99%では十分です。符号付き-符号なしの算術の問題ははるかに一般的であるため、避けるべきものの観点から優先されます。はい、コンパイラーはこれについて警告しますが、大規模なプロジェクトをコンパイルする際にいくつの警告が出ますか?警告を無視することは危険であり、そして悪い習慣が、現実の世界で...
エリアスヴァンOotegem

11
答えに+1。 注意先の鈍い意見:1:2番目の考え方に対する私の応答は次のとおりです。Cの符号なし整数型から予期しない結果を得る人は誰でも未定義の動作(純粋な学問的な種類ではない)符号付き整数型を使用する重要なCプログラム。あなたはその符号なしのタイプがあると考えるとCが十分わからない場合は、より良い使用のもの、私はC. 2を避け助言:配列インデックスとサイズCで、その者のために正確に一つの正しいタイプがありますsize_t、特殊なケースがありますしない限り、そうでなければ正当な理由。
mtraceur

5
混合署名なしで問題が発生します。unsigned int-unsigned intを計算するだけです。
gnasher729

4
サイモンは、「配列インデックスなど、本質的に署名されていない概念がいくつかある」と主張する最初の考え方でのみ、問題はありません。特に:「配列インデックスのために正確に一つの正しいタイプは... C、であります」 でたらめ!。DSPerは常に負のインデックスを使用します。特に、因果関係のない偶数または奇数の対称インパルス応答の場合。およびLUT数学用。私は2番目の流派にいますが、CとC ++で符号付き整数と符号なし整数の両方を使用すると便利だと思います。
ロバートブリストージョンソン

21

まず第一に、Google C ++コーディングガイドラインは従うにはあまり良いものではありません。現代のC ++の定番である例外やブーストなどを排除します。第二に、特定のガイドラインがX社に有効であるからといって、それがあなたにぴったりだというわけではありません。署名のないタイプの使用は引き続き必要です。

C ++の適切な経験則intは、他の何かを使用する正当な理由がない限り、好むということです。


8
それは私がまったく意味するものではありません。コンストラクタは不変式を確立するためのものであり、関数ではないためreturn false、その不変式が確立されていない場合は単純にできません。そのため、物事を分けてオブジェクトのinit関数を使用するか、をスローしてstd::runtime_errorスタックの巻き戻しを実行し、すべてのRAIIオブジェクトを自動的にクリーニングして、開発者が便利な場合に例外を処理できますあなたがそうする。
bstamour

5
アプリケーションのタイプがどのように違いをもたらすかはわかりません。オブジェクトのコンストラクターを呼び出すたびに、パラメーターを使用して不変式を確立します。その不変条件を満たせない場合は、エラーを通知する必要があります。そうでない場合、プログラムは良好な状態ではありません。コンストラクターはフラグを返すことができないため、例外をスローするのは自然なオプションです。ビジネスアプリケーションがこのようなコーディングスタイルの恩恵を受けない理由について、確固とした議論をお願いします。
bstamour

8
私は、C ++プログラマーの半分が例外を適切に使用できないことを非常に疑っています。しかし、とにかく、同僚が最新のC ++を書くことができないと思う場合は、必ず最新のC ++から離れてください。
bstamour 14

6
@ zzz777例外を使用しませんか?例外をキャッチし、何を返すかを行うパブリックファクトリ関数によるラッパーであるプライベートコンストラクターがありますnullptrか?「デフォルト」オブジェクトを返します(それが意味するものは何でも)。あなたは何も解決しませんでした-カーペットの下に問題を隠しただけで、誰も見つけられないことを願っています。
マエル

5
@ zzz777とにかくボックスをクラッシュさせようとしているのなら、なぜそれが例外から起こるのか気にするのsignal(6)ですか?例外を使用すると、対処方法を知っている開発者の50%が適切なコードを記述でき、残りのコードは同僚が持ち運ぶことができます。
IllusiveBrian

6

他の答えには実世界の例がないため、1つ追加します。私が(個人的に)符号なしの型を避けようとする理由の1つ。

標準のsize_tを配列インデックスとして使用することを検討してください。

for (size_t i = 0; i < n; ++i)
    // do something here;

わかりました、完全に普通。次に、何らかの理由でループの方向を変更することにしたことを検討してください。

for (size_t i = n - 1; i >= 0; --i)
    // do something here;

そして今では機能しません。intイテレータとして使用した場合、問題はありません。過去2年間でこのようなエラーが2回発生しました。実稼働環境で発生し、デバッグが困難でした。

私のもう1つの理由は、毎回このようなことを書くようにする迷惑な警告です:

int n = 123;  // for some reason n is signed
...
for (size_t i = 0; i < size_t(n); ++i)

これらはマイナーなものですが、それらは合計します。どこでも符号付き整数のみが使用されている場合、コードはよりクリーンであると感じます。

編集: 確かに、例は馬鹿げているように見えますが、私は人々がこの間違いを犯しているのを見ました。それを避ける簡単な方法があれば、それを使ってみませんか?

VS2015またはGCCで次のコードをコンパイルすると、デフォルトの警告設定で警告が表示されません(GCCの-Wallを使用しても)。GCCでこれに関する警告を得るには、-Wextraを要求する必要があります。これは、常にWallとWextraでコンパイルする必要がある理由の1つです(そして静的アナライザーを使用します)が、多くの実際のプロジェクトではそうしません。

#include <vector>
#include <iostream>


void unsignedTest()
{
    std::vector<int> v{ 1, 2 };

    for (int i = v.size() - 1; i >= 0; --i)
        std::cout << v[i] << std::endl;

    for (size_t i = v.size() - 1; i >= 0; --i)
        std::cout << v[i] << std::endl;
}

int main()
{
    unsignedTest();
    return 0;
}

署名された型ではさらに間違っている可能性があります...そして、あなたのサンプルコードは非常に頭がおかしく、警告を求めるとまともなコンパイラーは警告を発します。
デデュプリケーター

1
私は過去for (size_t i = n - 1; i < n; --i)に、それを正しく機能させるような恐怖に訴えてきました。
サイモンB

2
forループとsize_t逆に言えば、次のスタイルのコーディングガイドラインがありますfor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
-rwong

2
@rwong Omg、これはいです。なぜ単に使用しないのintですか?:)
アレクセイペトレンコ

1
@AlexeyPetrenko-現在のC標準もC ++標準も、のintすべての有効な値を保持するのに十分な大きさであることを保証していませんsize_t。特に、int最大2 ^ 15-1までの数値のみを許可する場合があり、2 ^ 16(または場合によってはそれ以上)のメモリ割り当て制限があるシステムで一般的に許可されます。 longまだ動作することが保証されていませが、より安全な賭けかもしれませsize_tすべてのプラットフォームおよびすべての場合でのみ動作することが保証されています。
ジュール

4
for (size_t i = v.size() - 1; i >= 0; --i)
   std::cout << v[i] << std::endl;

ここでの問題は、誤った動作を引き起こす巧妙な方法でループを記述したことです。ループの構築は、初心者が符号付きの型について教えられるようになっています(これはOKで正しいです)が、符号なしの値には適合しません。しかし、これは符号なしの型の使用に対する反論として機能することはできません。ここでのタスクは、単純にループを正しくすることです。そして、これは次のような署名されていない型で確実に動作するように簡単に修正できます

for (size_t i = v.size(); i-- > 0; )
    std::cout << v[i] << std::endl;

この変更は単純に比較とデクリメント操作のシーケンスを元に戻し、私の意見では、バックワードループで符号なしカウンターを処理するための最も効果的で邪魔にならず、クリーンでショートな方法です。whileループを使用する場合、まったく同じことを(直感的に)行います。

size_t i = v.size();
while (i > 0)
{
    --i;
    std::cout << v[i] << std::endl;
}

アンダーフローは発生せず、空のコンテナの場合は、符号付きカウンターループのよく知られたバリアントのように暗黙的にカバーされ、ループの本体は符号付きカウンターまたはフォワードループと比較して変更されないままです。最初は少し奇妙に見えるループ構造に慣れる必要があります。しかし、それを数十回見た後、もはや理解できないものはありません。

初心者向けコースが、署名された型だけでなく、署名されていない型に対しても正しいループを表示するようになれば幸運です。これにより、署名のないタイプを非難する代わりに、知らないうちの開発者に非難されるべきいくつかのエラーを回避できます。

HTH


1

符号なし整数は理由があります。

たとえば、ネットワークパケットやファイルバッファなどの個別のバイトとしてデータを渡すことを検討してください。時折、24ビット整数のような獣に遭遇するかもしれません。3つの8ビット符号なし整数から簡単にビットシフトできますが、8ビット符号付き整数ではそれほど簡単ではありません。

または、文字参照テーブルを使用するアルゴリズムについて考えてください。文字が8ビットの符号なし整数である場合、文字値でルックアップテーブルにインデックスを付けることができます。ただし、プログラミング言語が符号なし整数をサポートしていない場合はどうしますか?配列に負のインデックスがあります。まあ、私はあなたがのようなものを使用できると思いますが、それはただいですcharval + 128

実際、多くのファイル形式は符号なし整数を使用しており、アプリケーションプログラミング言語が符号なし整数をサポートしていない場合、問題になる可能性があります。

次に、TCPシーケンス番号を検討します。TCP処理コードを作成する場合は、符号なし整数を使用する必要があります。

場合によっては、効率が非常に重要になるため、余分なビットの符号なし整数が本当に必要になります。たとえば、数百万台で出荷されるIoTデバイスを検討してください。その後、多くのプログラミングリソースを、マイクロ最適化に費やすことを正当化できます。

符号なし整数型(混合符号演算、混合符号比較)の使用を避けるための正当化は、適切な警告を行うコンパイラーによって克服できると主張します。通常、このような警告はデフォルトでは有効になっていませんが、eg -Wextraまたはを個別に参照してください-Wsign-compare-WextraCでは自動的に有効になりますが、C ++では自動的に有効になりませんが)-Wsign-conversion

それでも、疑わしい場合は、署名されたタイプを使用してください。多くの場合、それはうまくいく選択です。そして、これらのコンパイラの警告を有効にしてください!


0

整数が実際に数値を表さない場合が多くありますが、たとえばビットマスク、idなどです。基本的に、整数に1を加算しても意味のない結果になる場合があります。これらの場合、符号なしを使用します。

整数を使用して算術演算を行う多くの場合があります。これらの場合、符号付き整数を使用して、ゼロ付近での誤動作を回避します。ループをゼロまで実行すると、非常に直感的でないコードを使用するか、符号なしの数値を使用しているために壊れる、ループの例を多数参照してください。「しかし、インデックスは決して負ではない」という議論があります-確かですが、たとえばインデックスの違いは負です。

インデックスが2 ^ 31を超えて2 ^ 32を超えない非常にまれなケースでは、符号なし整数を使用せず、64ビット整数を使用します。

最後に、良いトラップ:ループ内で「for(i = 0; i <n; ++ i)a [i] ...」 i = 2 ^ 32-1 iでラップアラウンドするため、ポインタをインクリメントすることによるa [i]へのアクセス。nがそれほど大きくならない場合でも。符号付き整数を使用すると、これを回避できます。


-5

最後に、私はここで本当に良い答えを見つけました:J.ViegaとM.Messierによる「Secure Programming Cookbook」(http://shop.oreilly.com/product/9780596003944.do

符号付き整数のセキュリティ問題:

  1. 関数が正のパラメーターを必要とする場合、低い範囲のチェックを忘れがちです。
  2. 負の整数サイズ変換からの直感的でないビットパターン。
  3. 負の整数の右シフト演算によって生成される直感的でないビットパターン。

符号付き<->符号なし変換には問題があるため、mixを使用することはお勧めできません。


1
なぜそれが良い答えなのですか?レシピ3.5とは何ですか?整数オーバーフローなどについて何と言っていますか?
バルドリック

私の実際の経験では、それは私が試した面で他のすべての貴重なアドバイスと非常に良い本であり、この勧告でかなりしっかりしています。4Gより長い配列での整数オーバーフローの危険性と比較すると、かなり弱いようです。そのような大きな配列を処理する必要がある場合、私のプログラムはパフォーマンスの低下を避けるために多くの微調整を行います。
zzz777

1
本が良いかどうかではありません。あなたの答えはレシピの使用を正当化するものではなく、誰もがそれを調べるための本のコピーを持っているわけではありません。良い答えを書く方法の例を見てください
Baldrickk

FYIだけ符号なし整数を使用してのもう一つの理由について学んだ:1は、簡単にoverlow検出することができます:youtube.com/...
zzz777
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.