Warum schlägt diese Bedingung fehl, wenn negative und positive Ganzzahlen verglichen werden sollen

8
%Vor%

Die Ausgabe nach dem Kompilieren mit gcc ist "more" . Warum die if Bedingung fehlschlägt, auch wenn -1 < 8 ?

    
manav m-n 15.08.2013, 07:14
quelle

6 Antworten

17

Das Problem ist in Ihrem Vergleich:

%Vor%

sizeof gibt normalerweise unsigned long zurück, so dass SIZE unsigned long ist, während -1 nur ein int ist. Die Regeln für die Werbung in C und verwandten Sprachen bedeuten, dass -1 vor dem Vergleich in size_t konvertiert wird, so dass -1 zu einem sehr großen positiven Wert wird (der Maximalwert von unsigned long ).

Eine Möglichkeit, dies zu beheben, besteht darin, den Vergleich zu ändern:

%Vor%

obwohl es eigentlich ein sinnloser Vergleich ist, da ein vorzeichenloser Wert per Definition immer & gt; = 0 ist und der Compiler Sie wahrscheinlich davor warnen kann.

Wie Sie nachfolgend von @Nobilis notiert haben, sollten Sie immer Compilerwarnungen aktivieren und darauf achten: Wenn Sie mit z. gcc -Wall ... Der Compiler hätte Sie vor Ihrem Fehler gewarnt.

    
Paul R 15.08.2013, 07:15
quelle
11

TL; DR

Seien Sie vorsichtig mit gemischt signierten / unsignierten Operationen (benutzen Sie -Wall Compiler Warnungen). Der Standard hat einen langen Abschnitt darüber. Insbesondere ist es oft, aber nicht immer richtig, dass signed in einen vorzeichenlosen Wert konvertiert wird (obwohl dies in Ihrem speziellen Beispiel der Fall ist). Siehe folgende Erklärung (aus diesem Q & A; )

Relevantes Zitat aus dem C ++ Standard:

5 Ausdrücke [expr]

  

10 Viele binäre Operatoren, die Operanden der Arithmetik oder erwarten   Aufzählungstyp verursacht Konvertierungen und Ergebnistypen ähnlich   Weg. Der Zweck ist es, einen gemeinsamen Typ zu ergeben, der auch die Art von ist   das Ergebnis. Dieses Muster wird die üblichen arithmetischen Umwandlungen genannt,   die wie folgt definiert sind:

[2 Klauseln über gleiche Typen oder Typen von Gleichheitszeichen entfallen]

  

- Andernfalls, wenn der Operand mit dem vorzeichenlosen Integer-Typ einen Rang hat   größer oder gleich dem Rang des Typs des anderen Operanden,   Der Operand mit dem Ganzzahl-Typ mit Vorzeichen muss in den Typ von konvertiert werden   der Operand mit Vorzeichen ohne Vorzeichen.

     

- Andernfalls, wenn der Typ von   Der Operand mit dem Ganzzahl-Typ mit Vorzeichen kann alle Werte darstellen   vom Typ des Operanden mit vorzeichenloser Ganzzahl, der Operand   mit vorzeichenlosen Integer Typ soll in den Typ der konvertiert werden   Operand mit vorzeichenbehafteten Integer-Typ.

     

- Ansonsten sollen beide Operanden sein   konvertiert in den vorzeichenlosen ganzzahligen Typ, der dem Typ von entspricht   der Operand mit dem Ganzzahl-Typ mit Vorzeichen.

Ihr aktuelles Beispiel

Um zu sehen, in welche der 3 Fälle Ihr Programm fällt, modifizieren Sie es leicht zu diesem

%Vor%

Auf der Coliru Online-Compiler, das druckt 4 und 8 für das sizeof() von -1 und SIZE bzw. und wählt den "mehr" Zweig (live Beispiel ).

Der Grund dafür ist, dass der vorzeichenlose Typ einen höheren Rang als der signierte Typ hat. Daher gilt Klausel 1 und der vorzeichenbehaftete Typ wird in den vorzeichenlosen Typ wertkonvertiert (bei der meisten Implementation, typischerweise durch Beibehalten der Bitdarstellung, also Umwickeln mit einer sehr großen vorzeichenlosen Zahl), und der Vergleich fährt dann fort, um den " mehr "Zweig.

Variationen zu einem Thema

Umschreiben der Bedingung if ((long long)(-1) < (unsigned)SIZE) würde die "weniger" Zweig ( Live-Beispiel ).

Der Grund dafür ist, dass der Typ mit Vorzeichen einen höheren Rang als der Typ ohne Vorzeichen hat und auch alle vorzeichenlosen Werte aufnehmen kann. Daher gilt Satz 2 und der vorzeichenlose Typ wird in den vorzeichenbehafteten Typ konvertiert, und der Vergleich fährt fort, um den "less" Zweig auszuwählen.

Natürlich würden Sie niemals eine so konstruierte if() -Anweisung mit expliziten Umwandlungen schreiben, aber derselbe Effekt könnte auftreten, wenn Sie Variablen mit den Typen long long und unsigned vergleichen. Es veranschaulicht also den Punkt, dass gemischte vorzeichenbehaftete / vorzeichenlose Arithmetik sehr subtil ist und von den relativen Größen abhängt ("Ranking" in den Wörtern des Standards). Insbesondere gibt es keine Regeln, die besagen, dass signed immer in unsigned umgewandelt wird.

    
TemplateRex 15.08.2013 09:07
quelle
7

Wenn Sie das tun Vergleich zwischen signed und unsigned , wobei unsigned mindestens ein gleichen Rang zu, dass der signed Typ (TemplateRex Antwort für die genaue Regeln sehen), wird das signed umgewandelt der Typ von unsigned .

Im Hinblick auf Ihren Fall auf einer 32-Bit-Maschine die binäre Darstellung von -1 in unsigned ist 4294967295. Also in der Tat Sie vergleichen, wenn 4294967295 kleiner als 8 (es ist nicht).

Wenn Sie Warnungen aktiviert hätten, wären Sie vom Compiler gewarnt worden, dass etwas faul ist:

warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

Da sich die Diskussion ein wenig verschoben hat, wie passend die Verwendung von unsigned ist, lassen Sie mich ein Zitat von James Gosling in Bezug auf das Fehlen von unsigned Typen in Java setzen (und ich werde schamlos link auf einem anderen Posten von mir zu diesem Thema):

  

Gosling: Für mich als Sprachdesigner, was ich nicht wirklich zähle   ich selbst wie in diesen Tagen, was "einfach" wirklich am Ende Bedeutung war, konnte   Ich erwarte, dass J. Random Developer die Spezifikation in seinem Kopf hält. Das   Die Definition besagt, dass zum Beispiel Java nicht - und tatsächlich eine Menge davon ist   Diese Sprachen enden mit einer Menge von Ecken Fällen, Dinge, die niemand   versteht wirklich. Quiz alle C-Entwickler über unsigniert und hübsch   Bald stellt sich heraus, dass fast keine C-Entwickler wirklich was verstehen   geht mit unsigned weiter, was vorzeichenlose Arithmetik ist. Sachen wie diese   machte C komplex. Der Sprachteil von Java ist, denke ich, ziemlich einfach.   Die Bibliotheken müssen Sie nachschlagen.

    
Nobilis 15.08.2013 07:16
quelle
6

Dies ist ein historischer Designfehler von C, der auch in C ++ wiederholt wurde.

Es geht auf 16-Bit-Computer zurück und der Fehler bestand darin, alle 16 Bits zu verwenden, um Größen von bis zu 65536 darzustellen, wodurch die Möglichkeit zur Darstellung negativer Größen aufgegeben wurde.

Dies wäre kein Fehler gewesen, wenn unsigned bedeutung "nicht negative Ganzzahl" wäre (eine Größe kann logisch nicht negativ sein), aber es ist ein Problem mit den Konvertierungsregeln der Sprache.

Angesichts der Konvertierungsregeln der Sprache stellt der Typ unsigned in C keine nicht negative Zahl dar, sondern eher eine Bitmaske (der mathematische Ausdruck ist eigentlich "ein Mitglied von ℤ/n ring "). Um zu sehen, warum das für die C- und C ++ - Sprache in Betracht gezogen wird

  • unsigned - unsigned gibt ein unsigned result
  • signed + unsigned gibt und unsigned result

beide ergeben eindeutig keinen Sinn, wenn Sie unsigned als "nicht negative Zahl" lesen.

Natürlich sagen, dass die Größe eines Objekts ein Mitglied von ℤ/n Ring ist macht überhaupt keinen Sinn und hier ist es, wo der Fehler liegt.

Praktische Auswirkungen:

Jedes Mal, wenn Sie mit der Größe eines Objekts arbeiten, seien Sie vorsichtig , da der Wert unsigned ist und dieser Typ in C / C ++ viele Eigenschaften enthält, die für eine Zahl unlogisch sind. Bitte denken Sie immer daran, dass unsigned nicht "nicht negative ganze Zahl" bedeutet, sondern "Mitglied von ℤ/n algebraischen Ring" und dass am gefährlichsten ist, dass im Falle einer gemischten Operation int in unsigned int und konvertiert wird nicht das Gegenteil.

Zum Beispiel:

%Vor%

ist fehlerhaft, denn wenn ein leerer Vektor von Punkten übergeben wird, werden ungültige (UB) Operationen ausgeführt. Der Grund ist, dass pts.size() ein unsigned ist.

Die Regeln der Sprache konvertieren 1 (eine Ganzzahl) in 1{mod n} , führen die Subtraktion in ℤ/n durch, was zu (size-1){mod n} führt, konvertiert i auch in eine {mod n} Darstellung und wird Mach den Vergleich in ℤ/n .

C / C ++ definiert tatsächlich einen < Operator in ℤ/n (selten in Mathe) und Sie werden am Ende auf pts[0] , pts[1] ... usw. zugreifen, bis große Zahlen, selbst wenn der Eingabevektor war leer.

Eine korrekte Schleife könnte

sein %Vor%

aber normalerweise bevorzuge ich

%Vor%

Mit anderen Worten, wir werden unsigned so schnell wie möglich los und arbeiten nur mit regulären Ints.

Verwenden Sie niemals unsigned , um die Größe von Containern oder Zählern darzustellen, da unsigned "Mitglied von ℤ/n " bedeutet und die Größe eines Containers nicht eines dieser Dinge ist. Unsignierte Typen sind nützlich, aber NICHT , um die Größe von Objekten darzustellen.

Die Standard-C / C ++ - Bibliothek hat diese Auswahl leider falsch getroffen, und es ist zu spät, um sie zu beheben. Sie sind jedoch nicht gezwungen, denselben Fehler zu machen.

In den Worten von Bjarne Stroustrup :

  

Verwenden Sie ein unsigned statt eines int, um ein weiteres Bit zur Darstellung zu erhalten   positive ganze Zahlen sind fast nie eine gute Idee. Versuche, das sicherzustellen   Einige Werte sind positiv, indem Variablen ohne Vorzeichen deklariert werden   in der Regel durch die impliziten Konvertierungsregeln besiegt werden

    
6502 15.08.2013 08:02
quelle
2

Nun, ich werde die starken Worte, die Paul R gesagt hat, nicht wiederholen, aber wenn du unsigned und ganze Zahlen vergleichst, wirst du schlechte Dinge erleben.

mach if ((-1) < (int)SIZE)

anstelle Ihrer if-Bedingung

    
No Idea For Name 15.08.2013 07:18
quelle
-1

Konvertiert den vorzeichenlosen Typ, der von sizeof operator zurückgegeben wurde, in signed

Wenn Sie zwei vorzeichenlose und vorzeichenbehaftete Zahlen vergleichen, wird der Compiler implizit in vorzeichenlos konvertiert.
-1 vorzeichenbehaftete Darstellung in 4 Byte int ist 11111111 11111111 11111111 11111111 Wenn diese in eine vorzeichenlose Darstellung umgewandelt wird, würde sich diese Darstellung auf 2 ^ 16-1 beziehen Also im Grunde vergleichen Sie das 2 ^ 16-1 & gt; SIZE, was wahr wäre.
Sie müssen dies überschreiben, indem Sie den vorzeichenlosen Wert explizit in signed umwandeln. Da sizeof operator unsigned long long zurückliefert, sollten Sie es auf signed long long

umwandeln %Vor%

Verwenden Sie diese wenn Bedingung in Ihrem Code

    
Himanshu Pandey 15.08.2013 07:21
quelle