Eine Warnung – Vergleich zwischen vorzeichenbehafteten und vorzeichenlosen ganzzahligen Ausdrücken
Lesezeit: 12 Minuten
Tim Harrington
Ich arbeite mich gerade durch Beschleunigtes C++ und sind in Übung 2-3 auf ein Problem gestoßen.
Ein kurzer Überblick über das Programm – Das Programm nimmt grundsätzlich einen Namen und zeigt dann eine Begrüßung innerhalb eines Rahmens von Sternchen an – dh Hallo! umgeben von * umrahmt.
Die Übung – Im Beispielprogramm verwenden die Autoren const int um den Abstand (Leerzeichen) zwischen der Begrüßung und den Sternchen festzulegen. Dann bitten sie den Leser, als Teil der Übung, den Benutzer um eine Eingabe zu bitten, wie groß die Polsterung sein soll.
All dies scheint einfach genug zu sein, ich frage den Benutzer nach zwei ganzen Zahlen (int) und speichere sie und ändere das Programm so, dass es diese Ganzzahlen verwendet, wobei die vom Autor verwendeten entfernt werden, obwohl ich beim Kompilieren die folgende Warnung erhalte;
Übung2-3.cpp:46: Warnung: Vergleich zwischen vorzeichenbehafteten und vorzeichenlosen ganzzahligen Ausdrücken
Nach einiger Recherche scheint es daran zu liegen, dass der Code versucht, eine der obigen Ganzzahlen zu vergleichen (int) zu einem string::size_type, was in Ordnung ist. Aber ich habe mich gefragt, ob das bedeutet, dass ich eine der ganzen Zahlen ändern sollte unsigned int? Ist es wichtig, explizit anzugeben, ob meine Ganzzahlen vorzeichenbehaftet oder vorzeichenlos sind?
cout << "Please enter the size of the frame between top and bottom you would like ";
int padtopbottom;
cin >> padtopbottom;
cout << "Please enter size of the frame from each side you would like: ";
unsigned int padsides;
cin >> padsides;
string::size_type c = 0; // definition of c in the program
if (r == padtopbottom + 1 && c == padsides + 1) { // where the error occurs
Oben sind die relevanten Codebits, die c ist vom Typ string::size_type weil wir nicht wissen, wie lang die Begrüßung sein könnte – aber warum bekomme ich dieses Problem jetzt, wenn der Code des Autors das Problem bei der Verwendung nicht hatte const int? Darüber hinaus – für alle, die abgeschlossen haben Beschleunigtes C++ – wird dies später im Buch erklärt?
Ich bin auf Linux Mint und verwende g++ über Geany, wenn das hilft oder einen Unterschied macht (wie ich gelesen habe, könnte es bei der Bestimmung, was string::size_type ist).
Würde man nicht davon ausgehen, dass Sie sowieso unsigned ints verwenden möchten? Ich kann mir keinen logischen Grund vorstellen, warum die Ober- und Unterseite negativ sein sollten
– Woot4Moo
7. September 10 um 17:08 Uhr
Das ist wahr und ich habe es im obigen Beitrag erwähnt, aber ich verstehe immer noch nicht, warum dieses Problem im Beispielprogramm des Autors nicht aufgetreten ist, als sie const int verwendet haben? Ich bin sicher, dass ich in dem Buch darauf eingehen werde, aber ich kann nicht umhin, neugierig zu sein.
– Tim Harrington
7. September 10 um 17:23 Uhr
Verschrotten Sie das – offensichtlich gab es in dieser Situation keine Warnung, weil der int immer 1 sein würde … oops.
– Tim Harrington
7. September 10 um 17:37 Uhr
Im Allgemeinen ist die Erhöhung der Reichweite den Aufwand nicht wert unsigned ganzzahlige Typen für Zählungen. Unsignierte Nummern haben auch ein garantiertes Wraparound-Verhalten, was sie geringfügig weniger effizient macht.
– Jon Purdy
7. September 10 um 17:48 Uhr
Der Autor hat möglicherweise dieselbe Warnung gesehen und sie einfach ignoriert. Gehen Sie nicht davon aus, dass die Autoren von Büchern sachkundiger oder vorsichtiger sind als der durchschnittliche Programmierer.
– Christoph Johnson
8. September 10 um 17:38 Uhr
Christoph Johnson
Es ist normalerweise eine gute Idee, Variablen als zu deklarieren unsigned oder size_t wenn sie mit Größen verglichen werden, um dieses Problem zu vermeiden. Verwenden Sie nach Möglichkeit genau den Typ, mit dem Sie vergleichen möchten (z. B. use std::string::size_type beim Vergleich mit a std::stringLänge).
Compiler warnen vor dem Vergleich von vorzeichenbehafteten und vorzeichenlosen Typen, weil die Bereiche von vorzeichenbehafteten und vorzeichenlosen Ganzzahlen unterschiedlich sind und wenn sie miteinander verglichen werden, können die Ergebnisse überraschend sein. Wenn Sie einen solchen Vergleich durchführen müssen, sollten Sie einen der Werte explizit in einen mit dem anderen kompatiblen Typ konvertieren, möglicherweise nachdem Sie überprüft haben, ob die Konvertierung gültig ist. Beispielsweise:
unsigned u = GetSomeUnsignedValue();
int i = GetSomeSignedValue();
if (i >= 0)
{
// i is nonnegative, so it is safe to cast to unsigned value
if ((unsigned)i >= u)
iIsGreaterThanOrEqualToU();
else
iIsLessThanU();
}
else
{
iIsNegative();
}
Ich weiß, dass der aktuelle C-Standard manchmal erfordert, dass negative vorzeichenbehaftete Werte größer verglichen werden als vorzeichenlose Werte, aber sollten Situationen, in denen dies auftritt, nicht als veraltet angesehen werden? Ich möchte, dass sich die Standards zumindest weiterentwickeln erlauben Compiler, um ein arithmetisch korrektes Verhalten zu erzeugen (das heißt, wenn der vorzeichenbehaftete Wert negativ ist, wird er kleiner verglichen, und wenn der vorzeichenlose Wert den Maximalwert des vorzeichenbehafteten Typs überschreitet, wird er größer verglichen). Es scheint seltsam, dass Compiler ohne explizite Typumwandlungen ein albernes Verhalten erzeugen müssen.
– Superkatze
13. Mai 2012 um 23:58 Uhr
@supercat: Da ganzzahlige Vergleiche zu einer einzigen Maschinenanweisung kompiliert werden und jedes Testen oder die Bearbeitung von Randfällen mehrere Maschinenanweisungen erfordern würde, wird das, was Sie vorschlagen, wahrscheinlich nicht als C-Funktion hinzugefügt … es könnte sicherlich nicht sein Standardverhalten, da es die Leistung unnötig beeinträchtigen würde, selbst wenn der Programmierer weiß, dass es nicht notwendig ist.
– Blake Miller
3. Februar 13 um 3:00 Uhr
@BlakeMiller: Code, der einen vorzeichenbehafteten und einen vorzeichenlosen Wert vergleichen möchte, als ob beide vorzeichenlos wären, könnte einen umwandeln und “mit voller Geschwindigkeit” ausführen. Andernfalls würde in vielen Fällen der Unterschied zwischen einem Vergleichen und Springen bestehen, bei dem zwei Befehle verwendet werden, gegenüber drei, was billiger wäre als Code, der die verschiedenen Fälle manuell behandelt.
– Superkatze
3. Februar 13 um 4:11 Uhr
@BlakeMiller: (Der Grund, warum ich zwei gegen drei sage, ist, dass der meiste Code, der zwei Zahlen vergleicht, eine Anweisung verwendet, um den Vergleich durchzuführen und darauf basierende Flags zu setzen; in vielen Fällen könnte ein Compiler die Dinge so arrangieren, dass vor dem Vergleich, das “sign”-Flag würde das obere Bit eines der Operanden enthalten, sodass ein einziger bedingter Sprung vor dem Vergleich ausreichen würde, um eine korrekte Semantik sicherzustellen). Beachten Sie, dass ein Compiler diejenige auswählen könnte, die am billigsten zu erreichen ist, da es eine Vielzahl von Möglichkeiten gibt, eine korrekte Semantik zu erreichen. Das Schreiben von C-Code für korrekte Semantik wäre schwieriger.
– Superkatze
3. Februar 13 um 17:53 Uhr
Nur um zu demonstrieren, dass “die Ergebnisse überraschend sein können”, wird das folgende Programm (nach dem Einfügen #include <cstdio> oben … und ich verwende g ++ 4.4.7), wird “true” ausgeben und angeben, dass es wahr ist, dass (signed) -1 größer als (unsigned) 12 ist: int main(int, char**) { int x = -1; unsigned int y = 12; printf("x > y: %sn", x > y ? "true":"false"); return 0; }
– villapx
24. Februar 16 um 22:44 Uhr
erich
Ich hatte gestern genau das gleiche Problem, als ich Problem 2-3 in Accelerated C++ durchgearbeitet habe. Der Schlüssel besteht darin, alle Variablen, die Sie vergleichen (mit booleschen Operatoren), in kompatible Typen zu ändern. In diesem Fall bedeutet das string::size_type (oder unsigned intaber da dieses Beispiel ersteres verwendet, bleibe ich einfach dabei, obwohl die beiden technisch kompatibel sind).
Beachten Sie, dass sie in ihrem ursprünglichen Code genau dies für den c-Zähler getan haben (Seite 30 in Abschnitt 2.5 des Buches), wie Sie zu Recht darauf hingewiesen haben.
Was dieses Beispiel komplizierter macht, ist, dass die verschiedenen Füllvariablen (padsides und padtopbottom) sowie alle Zähler verwendet werden müssen Auch geändert werden string::size_type.
Um zu Ihrem Beispiel zu gelangen, würde der von Ihnen gepostete Code am Ende so aussehen:
cout << "Please enter the size of the frame between top and bottom";
string::size_type padtopbottom;
cin >> padtopbottom;
cout << "Please enter size of the frame from each side you would like: ";
string::size_type padsides;
cin >> padsides;
string::size_type c = 0; // definition of c in the program
if (r == padtopbottom + 1 && c == padsides + 1) { // where the error no longer occurs
Beachten Sie, dass Sie in der vorherigen Bedingung den Fehler erhalten würden, wenn Sie die Variable r nicht als a initialisiert hätten string::size_type in dem for Schleife. Sie müssen also die for-Schleife mit etwas wie dem folgenden initialisieren:
for (string::size_type r=0; r!=rows; ++r) //If r and rows are string::size_type, no error!
Also, im Grunde, sobald Sie a einführen string::size_type Variable in den Mix einfügen, jedes Mal, wenn Sie eine boolesche Operation für dieses Element ausführen möchten, müssen alle Operanden einen kompatiblen Typ haben, damit es ohne Warnungen kompiliert werden kann.
Der wichtige Unterschied zwischen vorzeichenbehafteten und vorzeichenlosen Ganzzahlen ist die Interpretation des letzten Bits. Das letzte Bit bei vorzeichenbehafteten Typen stellt das Vorzeichen der Zahl dar, d. h.: z.
0001 ist 1 mit Vorzeichen und ohne Vorzeichen 1001 ist -1 mit Vorzeichen und 9 ohne Vorzeichen
(Ich habe das ganze Komplementproblem aus Gründen der Klarheit der Erklärung vermieden! So werden Ints im Speicher nicht genau dargestellt!)
Sie können sich vorstellen, dass es einen Unterschied macht, ob Sie mit -1 oder mit +9 vergleichen. In vielen Fällen sind Programmierer einfach zu faul, das Zählen von Ganzzahlen als vorzeichenlos zu deklarieren (wodurch der Kopf der for-Schleife aufgebläht wird). Dies ist normalerweise kein Problem, da Sie bei Ganzzahlen bis 2^31 zählen müssen, bis Ihr Vorzeichenbit Sie beißt. Deshalb ist es nur eine Warnung. Weil wir zu faul sind, ‘unsigned’ statt ‘int’ zu schreiben.
Ah, ich verstehe – ich habe jetzt die Zählung int als unsigned geändert. Wird dies als gute Praxis oder sogar als schlechte Praxis angesehen? 🙂
– Tim Harrington
7. September 10 um 17:35 Uhr
Bitte, wenn Sie ablehnen, erklären Sie kurz warum. Auch wenn es nur ein Wort ist. Ich kann nichts Falsches an meiner Antwort erkennen. Das könnte ein Problem sein, bei dem Sie mir helfen können.
– AndreasT
15. September ’10 um 8:00 Uhr
@Tim: “unsigned” ist ein Synonym für “unsigned int”. Sie sollten unsigned int oder den stl-Standard-Variablentyp std::size_t zum Zählen/Iterieren verwenden (was ebenfalls ein Synonym ist). Es hat sich bewährt, in allen Fällen von „Iterate over elements 0 to n“ unsigned zu verwenden. Es verbessert die Übersichtlichkeit und entfernt Warnungen, also ist es ein Gewinner 😉
– AndreasT
15. September 10 um 8:06 Uhr
Die interne Darstellung von vorzeichenbehafteten Ganzzahlen ist Compiler- (dh Maschinen-) abhängig. Ihre Notation mit einem Vorzeichenbit ist aufgrund einiger Probleme nicht weit verbreitet (+/- Null ist eines davon). Die meisten Maschinen verwenden einen Zweierkomplementbegriff, um negative Zahlen darzustellen. Der Vorteil ist, dass auch ohne Änderungen normale (vorzeichenlose) Arithmetik verwendet werden kann. -1 im 2er-Komplement wäre übrigens 1111.
– stn
19. Juli 11 um 18:49 Uhr
@AndreasT: Obwohl es verständlich ist, “das gesamte Komplementproblem aus Gründen der Klarheit zu vermeiden”, hätten Sie ein Beispiel verwenden können, das mit dem 2er-Komplement kompatibel ist, der Darstellung, die von praktisch allen Plattformen verwendet wird. 1001 für -1 war eine schlechte Wahl, eine viel bessere Wahl wäre “1111 entspricht -1 mit Vorzeichen und 15 ohne Vorzeichen”
– MestreLion
13. März 15 um 19:02 Uhr
Martin York
In den Extrembereichen kann ein Int ohne Vorzeichen größer werden als ein Int.
Daher generiert der Compiler eine Warnung. Wenn Sie sicher sind, dass dies kein Problem ist, können Sie die Typen gerne in denselben Typ umwandeln, damit die Warnung verschwindet (verwenden Sie C++-Umwandlung, damit sie leicht zu erkennen sind).
Alternativ können Sie die Variablen vom gleichen Typ machen, um den Compiler daran zu hindern, sich zu beschweren.
Ich meine, ist es möglich, eine negative Polsterung zu haben? Wenn ja, dann behalte es als int. Andernfalls sollten Sie wahrscheinlich unsigned int verwenden und den Stream die Situationen abfangen lassen, in denen der Benutzer eine negative Zahl eingibt.
Das Hauptproblem besteht darin, dass die zugrunde liegende Hardware, die CPU, nur Anweisungen zum Vergleichen von zwei vorzeichenbehafteten Werten oder zum Vergleichen von zwei vorzeichenlosen Werten hat. Wenn Sie der vorzeichenlosen Vergleichsanweisung einen vorzeichenbehafteten negativen Wert übergeben, wird dieser als große positive Zahl behandelt. Somit wird -1, das Bitmuster mit allen Bits an (Zweierkomplement), zum maximalen vorzeichenlosen Wert für die gleiche Anzahl von Bits.
8-Bit: -1 mit Vorzeichen sind die gleichen Bits wie 255 ohne Vorzeichen 16-Bit: -1 mit Vorzeichen sind die gleichen Bits wie 65535 ohne Vorzeichen usw.
Sie werden feststellen, dass cnt auf -1 gesetzt wird, wenn der read(2)-Aufruf fehlschlägt, weil der Dateideskriptor ungültig wird (oder ein anderer Fehler). Beim Vergleich mit sizeof(buf), einem vorzeichenlosen Wert, ist die if()-Anweisung falsch, da 0xffffffff nicht kleiner als sizeof() ist, eine (vernünftige, nicht auf maximale Größe ausgelegte) Datenstruktur.
Daher müssen Sie das obige if schreiben, um die signierte/unsignierte Warnung wie folgt zu entfernen:
1. Introduction of size_t and other datatypes was crafted to mostly work,
not engineered, with language changes, to be explicitly robust and
fool proof.
2. Overall, C/C++ data types should just be signed, as Java correctly
implemented.
Wenn Sie Werte haben, die so groß sind, dass Sie keinen funktionierenden Werttyp mit Vorzeichen finden können, verwenden Sie einen zu kleinen Prozessor oder eine zu große Menge an Werten in der Sprache Ihrer Wahl. Wenn, wie beim Geld, jede Ziffer zählt, gibt es in den meisten Sprachen Systeme, die Ihnen eine unendliche Genauigkeit bieten. C/C++ macht das einfach nicht gut, und Sie müssen alles rund um Typen sehr explizit machen, wie in vielen anderen Antworten hier erwähnt.
Würde man nicht davon ausgehen, dass Sie sowieso unsigned ints verwenden möchten? Ich kann mir keinen logischen Grund vorstellen, warum die Ober- und Unterseite negativ sein sollten
– Woot4Moo
7. September 10 um 17:08 Uhr
Das ist wahr und ich habe es im obigen Beitrag erwähnt, aber ich verstehe immer noch nicht, warum dieses Problem im Beispielprogramm des Autors nicht aufgetreten ist, als sie const int verwendet haben? Ich bin sicher, dass ich in dem Buch darauf eingehen werde, aber ich kann nicht umhin, neugierig zu sein.
– Tim Harrington
7. September 10 um 17:23 Uhr
Verschrotten Sie das – offensichtlich gab es in dieser Situation keine Warnung, weil der int immer 1 sein würde … oops.
– Tim Harrington
7. September 10 um 17:37 Uhr
Im Allgemeinen ist die Erhöhung der Reichweite den Aufwand nicht wert
unsigned
ganzzahlige Typen für Zählungen. Unsignierte Nummern haben auch ein garantiertes Wraparound-Verhalten, was sie geringfügig weniger effizient macht.– Jon Purdy
7. September 10 um 17:48 Uhr
Der Autor hat möglicherweise dieselbe Warnung gesehen und sie einfach ignoriert. Gehen Sie nicht davon aus, dass die Autoren von Büchern sachkundiger oder vorsichtiger sind als der durchschnittliche Programmierer.
– Christoph Johnson
8. September 10 um 17:38 Uhr