Vorzeichenbehaftete / vorzeichenlose Vergleiche

84

Ich versuche zu verstehen, warum der folgende Code an der angegebenen Stelle keine Warnung ausgibt.

//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;

Ich dachte, es hätte mit Hintergrundwerbung zu tun, aber die letzten beiden scheinen etwas anderes zu sagen.

Meiner Meinung nach ist der erste ==Vergleich genauso eine signierte / nicht signierte Nichtübereinstimmung wie die anderen?

Peter
quelle
3
gcc 4.4.2 druckt eine Warnung aus, wenn sie mit '-Wall'
aufgerufen wird
Dies ist Spekulation, aber vielleicht optimiert es alle Vergleiche, da es die Antwort zur Kompilierungszeit kennt.
Null Set
2
Ah! Re. Bobahs Kommentar: Ich habe alle Warnungen aktiviert und die fehlende Warnung wird jetzt angezeigt. Ich bin der Meinung, dass es auf der gleichen Warnstufe wie die anderen Vergleiche hätte erscheinen sollen.
Peter
1
@bobah: Ich hasse es wirklich, dass gcc 4.4.2 diese Warnung druckt (ohne die Möglichkeit, sie nur wegen Ungleichheit auszudrucken), da alle Möglichkeiten, diese Warnung zum Schweigen zu bringen, die Situation verschlimmern . Die Standardwerbung konvertiert zuverlässig sowohl -1 als auch ~ 0 in den höchstmöglichen Wert eines nicht signierten Typs. Wenn Sie die Warnung jedoch zum Schweigen bringen, indem Sie sie selbst wirken, müssen Sie den genauen Typ kennen. Wenn Sie also den Typ ändern (sagen Sie ihn auf unsigned long long erweitern), -1funktionieren Ihre Vergleiche mit bare weiterhin (aber diese geben eine Warnung), während Ihre Vergleiche mit -1uoder (unsigned)-1beide kläglich fehlschlagen.
Jan Hudec
Ich weiß nicht, warum Sie eine Warnung benötigen und warum Compiler sie einfach nicht zum Laufen bringen können. -1 ist negativ, also kleiner als jede vorzeichenlose Zahl. Einfache.
CashCow

Antworten:

92

Beim Vergleich von vorzeichenbehafteten mit vorzeichenlosen Werten konvertiert der Compiler den vorzeichenbehafteten Wert in vorzeichenlosen Wert. Für die Gleichstellung spielt dies keine Rolle -1 == (unsigned) -1. Für andere Vergleiche ist es wichtig, zB gilt : -1 > 2U.

EDIT: Referenzen:

5/9: (Ausdrücke)

Viele binäre Operatoren, die Operanden vom Typ Arithmetik oder Aufzählung erwarten, verursachen Konvertierungen und ergeben auf ähnliche Weise Ergebnistypen. Der Zweck besteht darin, einen gemeinsamen Typ zu erhalten, der auch der Typ des Ergebnisses ist. Dieses Muster wird als übliche arithmetische Konvertierung bezeichnet, die wie folgt definiert ist:

  • Wenn einer der Operanden vom Typ long double ist, wird der andere in long double konvertiert.

  • Wenn andernfalls einer der Operanden doppelt ist, wird der andere in double konvertiert.

  • Andernfalls wird, wenn einer der Operanden float ist, der andere in float konvertiert.

  • Andernfalls werden die integralen Beförderungen (4.5) für beide Operanden durchgeführt.54)

  • Wenn dann einer der Operanden lange ohne Vorzeichen ist, wird der andere in Long ohne Vorzeichen konvertiert.

  • Wenn andernfalls ein Operand ein Long-Int und der andere ein Int ohne Vorzeichen ist, muss das Int ohne Vorzeichen in ein Long-Int konvertiert werden, wenn ein Long-Int alle Werte eines Int ohne Vorzeichen darstellen kann. Andernfalls werden beide Operanden in unsigned long int konvertiert.

  • Andernfalls wird, wenn einer der Operanden lang ist, der andere in long konvertiert.

  • Andernfalls wird, wenn einer der Operanden ohne Vorzeichen ist, der andere in einen Vorzeichen ohne Vorzeichen konvertiert.

4.7 / 2: (Integrale Konvertierungen)

Wenn der Zieltyp ohne Vorzeichen ist, ist der resultierende Wert die am wenigsten vorzeichenlose Ganzzahl, die mit der Quell-Ganzzahl kongruent ist (Modulo 2 n, wobei n die Anzahl der Bits ist, die zur Darstellung des vorzeichenlosen Typs verwendet werden). [Hinweis: In der Zweierkomplementdarstellung ist diese Konvertierung konzeptionell und das Bitmuster ändert sich nicht (wenn keine Kürzung vorliegt). ]]

EDIT2: MSVC-Warnstufen

Was bei den verschiedenen Warnstufen von MSVC gewarnt wird, sind natürlich die Entscheidungen der Entwickler. Aus meiner Sicht sind ihre Entscheidungen in Bezug auf signierte / nicht signierte Gleichheit im Vergleich zu größeren / weniger Vergleichen sinnvoll. Dies ist natürlich völlig subjektiv:

-1 == -1bedeutet das gleiche wie -1 == (unsigned) -1- ich finde das ein intuitives Ergebnis.

-1 < 2 bedeutet nicht dasselbe wie -1 < (unsigned) 2- Dies ist auf den ersten Blick weniger intuitiv und IMO verdient eine "frühere" Warnung.

Erik
quelle
Wie können Sie signierte in nicht signierte konvertieren? Was ist die vorzeichenlose Version des vorzeichenbehafteten Werts -1? (Vorzeichen -1 = 1111, vorzeichenlos 15 = 1111, bitweise mögen sie gleich sein, aber sie sind logisch nicht gleich.) Ich verstehe, dass es funktionieren wird, wenn Sie diese Konvertierung erzwingen, aber warum sollte der Compiler das tun? Es ist unlogisch. Außerdem, wie ich oben kommentiert habe, erschien, als ich die Warnungen aufdeckte, die fehlende == Warnung, die zu stützen scheint, was ich sage?
Peter
Wie 4,7 / 2 sagen, bedeutet vorzeichenlos bis vorzeichenlos keine Änderung des Bitmusters für das Zweierkomplement. Warum der Compiler dies tut, wird vom C ++ - Standard verlangt. Ich glaube, die Begründung für die Warnungen von VS auf verschiedenen Ebenen ist die Möglichkeit, dass ein Ausdruck unbeabsichtigt ist - und ich stimme ihnen zu, dass der Gleichheitsvergleich von signiert / nicht signiert "weniger wahrscheinlich" ein Problem darstellt als Ungleichheitsvergleiche. Dies ist natürlich subjektiv - dies sind Entscheidungen, die von den VC-Compiler-Entwicklern getroffen wurden.
Erik
Ok, ich glaube ich verstehe es fast. Die Art und Weise, wie ich das lese, ist, dass der Compiler (konzeptionell) Folgendes tut: 'if (((unsigned _int64) 0x7fffffff) == ((unsigned _int64) 0xffffffff))', weil _int64 der kleinste Typ ist, der sowohl 0x7fffffff als auch 0xffffffff darstellen kann in unsignierten Begriffen?
Peter
2
Tatsächlich ist der Vergleich mit (unsigned)-1oder -1uoft schlechter als der Vergleich mit -1. Das liegt daran (unsigned __int64)-1 == -1, aber (unsigned __int64)-1 != (unsigned)-1. Wenn der Compiler also eine Warnung ausgibt, versuchen Sie, diese durch Umwandlung in unsigned oder using zum Schweigen zu bringen. -1uWenn der Wert tatsächlich 64-Bit ist oder Sie ihn später in einen ändern, wird Ihr Code beschädigt! Und denken Sie daran, dass size_t64-Bit ohne Vorzeichen nur auf 64-Bit-Plattformen verwendet wird und die Verwendung von -1 für ungültige Werte sehr häufig ist.
Jan Hudec
1
Vielleicht sollten cpmpilers das dann nicht tun. Wenn vorzeichenbehaftete und vorzeichenlose Werte verglichen werden, überprüfen Sie einfach, ob der vorzeichenbehaftete Wert negativ ist. Wenn ja, ist es garantiert weniger als das nicht signierte, unabhängig davon.
CashCow
31

Warum signierte / nicht signierte Warnungen wichtig sind und Programmierer sie beachten müssen, zeigt das folgende Beispiel.

Erraten Sie die Ausgabe dieses Codes?

#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;
}

Ausgabe:

i is greater than j

Überrascht? Online-Demo: http://www.ideone.com/5iCxY

Fazit: Wenn im Vergleich ein Operand ist unsigned, wird der andere Operand implizit in konvertiert, unsigned wenn sein Typ signiert ist!

Nawaz
quelle
2
Er hat recht! Es ist dumm, aber er hat recht. Dies ist ein wichtiger Punkt, auf den ich noch nie gestoßen bin. Warum konvertiert es das vorzeichenlose nicht in einen (größeren) vorzeichenbehafteten Wert?! Wenn Sie "if (i <((int) j))" ausführen, funktioniert dies wie erwartet. Obwohl "if (i <((_int64) j))" sinnvoller wäre (vorausgesetzt, was Sie nicht können, ist _int64 doppelt so groß wie int).
Peter
6
@Peter "Warum konvertiert es den Unsgiend nicht in einen (größeren) signierten Wert?" Die Antwort ist einfach: Möglicherweise gibt es keinen größeren vorzeichenbehafteten Wert. Auf einem 32-Bit-Computer waren in den Tagen zuvor sowohl int als auch long 32 Bit, und es gab nichts Größeres. Beim Vergleich von signierten und nicht signierten konvertierten die frühesten C ++ - Compiler beide in signierte. Weil ich vergesse, aus welchen Gründen das C-Normungskomitee dies geändert hat. Ihre beste Lösung ist es, unsignierte so weit wie möglich zu vermeiden.
James Kanze
5
@JamesKanze: Ich vermute, es hat auch etwas mit der Tatsache zu tun, dass das Ergebnis eines vorzeichenbehafteten Überlaufs undefiniertes Verhalten ist, während das Ergebnis eines vorzeichenlosen Überlaufs nicht ist, und daher die Umwandlung des negativen vorzeichenbehafteten Werts in einen vorzeichenlosen Wert definiert ist, während die Umwandlung eines großen vorzeichenlosen Werts in einen negativ vorzeichenbehafteten Wert definiert wird Wert ist nicht .
Jan Hudec
2
@James Der Compiler könnte immer eine Assembly generieren, die die intuitivere Semantik dieses Vergleichs implementiert, ohne einen größeren Typ zu verwenden. In diesem speziellen Beispiel würde es ausreichen, zuerst zu prüfen, ob i<0. Dann iist kleiner als jsicher. Wenn ies nicht kleiner als Null ist, ìkann es sicher in vorzeichenloses konvertiert werden, um es mit zu vergleichen j. Sicher, Vergleiche zwischen signierten und nicht signierten wären langsamer, aber ihr Ergebnis wäre in gewissem Sinne korrekter.
Sven
@Sven stimme ich zu. Der Standard hätte verlangen können, dass die Vergleiche für alle tatsächlichen Werte funktionieren, anstatt in einen der beiden Typen zu konvertieren. Dies würde jedoch nur für Vergleiche funktionieren; Ich vermute, dass das Komitee keine unterschiedlichen Regeln für Vergleiche und andere Operationen wollte (und das Problem der Angabe von Vergleichen nicht angreifen wollte, wenn der tatsächlich verglichene Typ nicht existierte).
James Kanze
4

Der Operator == führt nur einen bitweisen Vergleich durch (durch einfache Division, um festzustellen, ob er 0 ist).

Die kleineren / größeren als Vergleiche hängen viel mehr vom Vorzeichen der Zahl ab.

4 Bit Beispiel:

1111 = 15? oder -1?

Wenn Sie also 1111 <0001 haben ... ist es mehrdeutig ...

aber wenn Sie 1111 == 1111 haben ... Es ist das gleiche, obwohl Sie es nicht so gemeint haben.

Yochai Timmer
quelle
Ich verstehe das, aber es beantwortet meine Frage nicht. Wie Sie hervorheben, ist 1111! = 1111, wenn die Zeichen nicht übereinstimmen. Der Compiler weiß, dass die Typen nicht übereinstimmen. Warum warnt er also nicht davor? (Mein Punkt ist, dass mein Code viele solche Fehlanpassungen enthalten könnte, vor denen ich nicht gewarnt werde.)
Peter
So ist es gestaltet. Der Gleichheitstest prüft die Ähnlichkeit. Und es ist ähnlich. Ich stimme Ihnen zu, dass es nicht so sein sollte. Sie könnten ein Makro oder etwas machen, das x == y überlädt, um zu sein! ((X <y) || (x> y))
Yochai Timmer
1

In einem System, das die Werte mit 2-Komplementen darstellt (modernste Prozessoren), sind sie auch in ihrer binären Form gleich. Dies kann der Grund sein, warum sich der Compiler nicht über a == b beschwert .

Und für mich ist es ein seltsamer Compiler, der Sie nicht vor a == ((int) b) warnt . Ich denke, es sollte Ihnen eine Ganzzahl-Kürzungswarnung oder so geben.

Hossein
quelle
1
Die Philosophie von C / C ++ lautet: Der Compiler vertraut darauf, dass der Entwickler weiß, was er tut, wenn er explizit zwischen Typen konvertiert. Daher keine Warnung (zumindest standardmäßig - ich glaube, es gibt Compiler, die Warnungen dafür generieren, wenn die Warnstufe höher als die Standardeinstellung eingestellt ist).
Péter Török
0

Die betreffende Codezeile generiert keine C4018-Warnung, da Microsoft eine andere Warnnummer (z. B. C4389 ) verwendet hat, um diesen Fall zu behandeln, und C4389 standardmäßig nicht aktiviert ist (dh auf Stufe 3).

Aus den Microsoft-Dokumenten für C4389:

// 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;
};

Die anderen Antworten haben recht gut erklärt, warum Microsoft möglicherweise beschlossen hat, aus dem Gleichheitsoperator einen Sonderfall zu machen, aber ich finde, dass diese Antworten nicht besonders hilfreich sind, ohne C4389 zu erwähnen oder wie man es in Visual Studio aktiviert .

Ich sollte auch erwähnen, dass Sie, wenn Sie C4389 aktivieren möchten, möglicherweise auch die Aktivierung von C4388 in Betracht ziehen. Leider gibt es keine offizielle Dokumentation für C4388, aber es scheint in Ausdrücken wie den folgenden aufzutauchen:

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388
Tim Rae
quelle