web-dev-qa-db-ja.com

CおよびC ++での符号なし整数の使用

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

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

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

だから、私は正しいことを理解できませんでした:私たちの環境で符号付きの値を使用することは非常に不便であり、同時にコーディングガイドはこれを厳密に行うように主張する必要があります。

23
zzz777

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

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

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

31
Simon B

まず第一に、Google C++コーディングガイドラインは、従うのにあまり良いガイドラインではありません。例外、ブーストなど、最新のC++の主要なものを回避します。第2に、特定のガイドラインがX社で機能するからといって、それが適切であるとは限りません。必要に応じて、符号なしの型を引き続き使用します。

C++の適切な経験則は、他のものを使用する十分な理由がない限り、intを優先することです。

21
bstamour

他の回答には実世界の例がないため、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;
}
6
for (size_t i = v.size() - 1; i >= 0; --i)
   std::cout << v[i] << std::endl;

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

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

4
Don Pedro

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

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

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

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

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

時には、効率が非常に重要になるため、符号なし整数の余分なビットが本当に必要になります。たとえば、数百万で出荷されるIoTデバイスを考えてみます。多数のプログラミングリソースを使用して、マイクロ最適化に費やすことが正当化されます。

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

それでも、疑問がある場合は、署名された型を使用してください。多くの場合、それはうまく機能する選択です。そして、それらのコンパイラ警告を有効にしてください!

1
juhist

整数が実際に数値を表していない場合が多くありますが、たとえばビットマスクやIDなどです。基本的に、整数に1を追加しても意味のある結果にならない場合があります。そのような場合は、unsignedを使用してください。

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

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

最後に、ニーストラップ:ループで「for(i = 0; i <n; ++ i)a [i] ...」iが符号なし32ビットで、メモリが32ビットアドレスを超える場合、コンパイラーは最適化できませんポインタをインクリメントすることによる[i]へのアクセス。これは、i = 2 ^ 32-1でラップするためです。 nが決して大きくならない場合でも。符号付き整数を使用すると、これを回避できます。

0
gnasher729