符号付き/符号なしの比較


85

次のコードが指定された場所で警告を発行しない理由を理解しようとしています。

//from limits.h
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define INT_MAX  2147483647 /* maximum (signed) int value */
            /* = 0x7fffffff */

int a = INT_MAX;
//_int64 a = INT_MAX; // makes all warnings go away
unsigned int b = UINT_MAX;
bool c = false;

if(a < b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a > b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a <= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a >= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a == b) // no warning <--- warning expected here
    c = true;
if(((unsigned int)a) == b) // no warning (as expected)
    c = true;
if(a == ((int)b)) // no warning (as expected)
    c = true;

バックグラウンドプロモーションと関係があると思いましたが、最後の2つはそうではないと言っているようです。

私の考えでは、最初の==比較は他の比較と同じくらい符号付き/符号なしの不一致ですか?


3
gcc 4.4.2は、「
Wall

これは推測ですが、コンパイル時に答えがわかっているため、すべての比較が最適化されている可能性があります。
空集合

2
ああ!再。bobahのコメント:すべての警告をオンにすると、欠落している警告が表示されるようになりました。他の比較と同じ警告レベル設定で表示されるべきだったと思います。
ピーター

1
@bobah:gcc 4.4.2がその警告を出力するのは本当に嫌いです(不平等のためにのみ出力するように指示する方法はありません)。なぜなら、その警告を消音するすべての方法が事態を悪化させるからです。デフォルトのプロモーションでは、-1または〜0の両方が、符号なしタイプの可能な最大値に確実に変換されますが、警告を自分でキャストして無音にする場合は、正確なタイプを知っている必要があります。したがって、タイプを変更した場合(unsigned long longと言うように拡張)、bare-1との比較は引き続き機能します(ただし、警告が表示されます)が、-1uまたは(unsigned)-1両方の比較は惨めに失敗します。
Jan Hudec 2011年

なぜ警告が必要なのか、そしてなぜコンパイラが警告を機能させることができないのかわかりません。-1は負であるため、符号なしの数値よりも小さくなります。シンプル。
CashCow 2014

回答:


95

符号付きと符号なしを比較する場合、コンパイラは符号付きの値を符号なしに変換します。平等のために、これは重要ではありません、-1 == (unsigned) -1。他の比較については重要です。たとえば、次のことが当てはまります-1 > 2U

編集:参照:

5/9 :(表現)

算術型または列挙型のオペランドを期待する多くの二項演算子は、同様の方法で変換を引き起こし、結果型を生成します。目的は、結果のタイプでもある共通のタイプを生成することです。このパターンは通常の算術変換と呼ばれ、次のように定義されます。

  • いずれかのオペランドがlongdouble型の場合、もう一方はlongdoubleに変換されます。

  • それ以外の場合、一方のオペランドがdoubleの場合、もう一方はdoubleに変換されます。

  • それ以外の場合、一方のオペランドがfloatの場合、もう一方はfloatに変換されます。

  • それ以外の場合、積分昇格(4.5)は両方のオペランドで実行されるものとします54)。

  • 次に、一方のオペランドがunsigned longの場合、もう一方はunsignedlongに変換されます。

  • それ以外の場合、一方のオペランドがlong intで、もう一方のunsigned intである場合、longintがunsignedintのすべての値を表すことができる場合、unsignedintはlongintに変換されます。それ以外の場合は、両方のオペランドがunsigned longintに変換されます。

  • それ以外の場合、一方のオペランドが長い場合、もう一方はlongに変換されます。

  • それ以外の場合、一方のオペランドが符号なしの場合、もう一方は符号なしに変換されます。

4.7 / 2 :(積分変換)

宛先タイプが符号なしの場合、結果の値はソース整数と合同な最小の符号なし整数です(モジュロ2 n、nは符号なしタイプを表すために使用されるビット数)。[注:2の補数表現では、この変換は概念的なものであり、ビットパターンに変更はありません(切り捨てがない場合)。]

EDIT2:MSVC警告レベル

MSVCのさまざまな警告レベルで警告されるのは、もちろん、開発者による選択です。私が見ているように、符号付き/符号なしの平等と大きい/小さい比較に関する彼らの選択は理にかなっています、これはもちろん完全に主観的です:

-1 == -1と同じ意味です-1 == (unsigned) -1-直感的な結果だと思います。

-1 < 2 と同じ意味ではありません-1 < (unsigned) 2-これは一見直感的ではなく、IMOは「早期の」警告に値します。


どうすれば符号付きから符号なしに変換できますか?符号付きの値-1の符号なしバージョンは何ですか?(符号付き-1 = 1111、符号なし15 = 1111、ビット単位では等しい場合がありますが、論理的には等しくありません。)この変換を強制すると機能することは理解していますが、コンパイラがそれを行うのはなぜですか?それは非論理的です。さらに、上でコメントしたように、警告を表示すると、欠落している==警告が表示されました。これは、私が言ったことを裏付けているようです。
ピーター

1
4.7 / 2が言うように、符号付きから符号なしへは、2​​の補数のビットパターンに変化がないことを意味します。コンパイラがこれを行う理由については、C ++標準で要求されています。さまざまなレベルでのVSの警告の背後にある理由は、式が意図しない可能性があることだと思います。符号付き/符号なしの等式比較は、不等式比較よりも問題になる可能性が「低い」ということに同意します。もちろん、これは主観的なものです。これらは、VCコンパイラの開発者が行った選択です。
エリック

わかりました、私はほとんどそれを得ると思います。私がそれを読む方法は、コンパイラが(概念的に)次のことを行っていることです: 'if(((unsigned _int64)0x7fffffff)==((unsigned _int64)0xffffffff))'、なぜなら_int64は0x7fffffffと0xffffffffの両方を表すことができる最小の型だからです符号なしの用語で?
ピーター

2
実際に比較する(unsigned)-1か、またはと比較するより-1u悪いことがよくあり-1ます。なぜなら(unsigned __int64)-1 == -1、しかし(unsigned __int64)-1 != (unsigned)-1。したがって、コンパイラが警告を発した場合は、unsignedにキャストするか、を使用して無音化しようとし-1uます。値が実際に64ビットであるか、後で1に変更すると、コードが破損します。また、これsize_tは符号なしであり、64ビットプラットフォームでのみ64ビットであり、無効な値に-1を使用することは非常に一般的です。
Jan Hudec 2011年

1
たぶんcpmpilersはそれをするべきではありません。符号付きと符号なしを比較する場合は、符号付きの値が負であるかどうかを確認してください。もしそうなら、それは関係なく署名されていないものよりも少ないことが保証されています。
CashCow 2014

32

署名付き/署名なしの警告が重要であり、プログラマーがそれらに注意を払わなければならない理由は、次の例で示されています。

このコードの出力を推測しますか?

#include <iostream>

int main() {
        int i = -1;
        unsigned int j = 1;
        if ( i < j ) 
            std::cout << " i is less than j";
        else
            std::cout << " i is greater than j";

        return 0;
}

出力:

i is greater than j

びっくり?オンラインデモ:http//www.ideone.com/5iCxY

結論:比較すると、一方のオペランドがunsigned、の場合、もう一方のオペランドはunsigned その型が符号付きの場合に暗黙的に変換されます。


2
彼は正しい!それはばかげていますが、彼は正しいです。これは私が今まで出会ったことのない大きな落とし穴です。符号なしを(より大きな)符号付きの値に変換しないのはなぜですか?!「if(i <((int)j))」を実行すると、期待どおりに機能します。「if(i <((_ int64)j))」の方が理にかなっていますが(できませんが、_int64はintの2倍のサイズであると仮定します)。
ピーター

6
@Peter「なぜunsgiendを(より大きな)符号付きの値に変換しないのですか?」答えは簡単です。より大きな符号付きの値はないかもしれません。32ビットマシンでは、ずっと前の時代には、intとlongの両方が32ビットであり、それ以上のものはありませんでした。符号付きと符号なしを比較すると、初期のC ++コンパイラは両方を符号付きに変換しました。理由を忘れたので、C標準委員会がこれを変更しました。最善の解決策は、署名されていないものをできるだけ避けることです。
James Kanze 2011年

5
@JamesKanze:符号付きオーバーフローの結果は未定義の振る舞いであり、符号なしオーバーフローの結果はそうではないという事実にも関係があると思います。したがって、負の符号付き値から符号なしへの変換は、大きな符号なし値から負の符号付きへの変換中に定義されます。値はではありません
Jan Hudec 2011年

2
@Jamesコンパイラは、より大きな型にキャストすることなく、この比較のより直感的なセマンティクスを実装するアセンブリを常に生成できます。この特定の例では、最初にi<0。かどうかを確認するだけで十分です。その後、確かiよりも小さいですjiがゼロ以上の場合はì、unsignedに安全に変換して、と比較できますj。確かに、符号付きと符号なしの比較は遅くなりますが、ある意味でそれらの結果はより正確になります。
2014

@Sven同意します。標準では、2つのタイプのいずれかに変換するのではなく、すべての実際の値に対して比較が機能することを要求できた可能性があります。ただし、これは比較のためにのみ機能します。委員会は、比較やその他の操作に異なるルールを望んでいなかったと思います(そして、実際に比較されているタイプが存在しないときに比較を指定する問題を攻撃したくありませんでした)。
James Kanze 2014

4

==演算子は、ビット単位の比較を行います(0であるかどうかを確認するための単純な除算による)。

比較よりも小さい/大きいは、数値の符号に大きく依存します。

4ビットの例:

1111 = 15?または-1?

したがって、1111 <0001の場合、あいまいです...

しかし、あなたが1111 == 1111を持っているなら...あなたがそうするつもりはなかったけれども、それは同じことです。


私はこれを理解していますが、それは私の質問に答えません。ご指摘のとおり、符号が一致しない場合は1111!= 1111です。コンパイラは型との不一致があることを知っているのに、なぜそれについて警告しないのですか?(私のコードには、警告されていないような多くの不一致が含まれている可能性があるというのが私のポイントです。)
Peter

それはそれが設計された方法です。同等性テストは類似性をチェックします。そしてそれは似ています。私はそれがこのようであってはならないことに同意します。x == yをオーバーロードするマクロまたは何かを実行して!((x <y)||(x> y))
Yochai Timmer 2011年

1

2の補数(最新のプロセッサ)を使用して値を表すシステムでは、バイナリ形式でも値は等しくなります。これが、コンパイラがa == bについて文句を言わない理由かもしれません。

そして私には、奇妙なコンパイラが==((int)b)について警告しないのです。整数の切り捨ての警告などが表示されるはずです。


1
C / C ++の哲学は次のとおりです。コンパイラーは、開発者が型間を明示的に変換するときに何をしているのかを知っていると信頼しています。したがって、警告はありません(少なくともデフォルトでは、警告レベルがデフォルトよりも高く設定されている場合、これに対して警告を生成するコンパイラがあると思います)。
ペーテルTörök

0

問題のコード行は、Microsoftがそのケースを処理するために別の警告番号(つまりC4389)を使用しており、C4389がデフォルトで有効になっていない(つまりレベル3)ため、C4018警告を生成しません。

C4389のMicrosoftドキュメントから:

// C4389.cpp
// compile with: /W4
#pragma warning(default: 4389)

int main()
{
   int a = 9;
   unsigned int b = 10;
   if (a == b)   // C4389
      return 0;
   else
      return 0;
};

他の回答は、Microsoftが等式演算子から特別なケースを作成することを決定した理由を非常によく説明していますが、C4389またはVisual Studioでそれを有効にする方法に言及しない限り、これらの回答はあまり役に立ちません。

また、C4389を有効にする場合は、C4388を有効にすることも検討してください。残念ながら、C4388の公式ドキュメントはありませんが、次のような表現でポップアップするようです。

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.