Ein Warnvergleich zwischen vorzeichenbehafteten und vorzeichenlosen Ganzzahlausdrücken

77

Ich arbeite derzeit an Accelerated C ++ und bin in Übung 2-3 auf ein Problem gestoßen.

Ein kurzer Überblick über das Programm - das Programm nimmt im Grunde genommen einen Namen an und zeigt dann eine Begrüßung in einem Rahmen aus Sternchen an - dh Hallo! umgeben von * 's gerahmt.

Die Übung - Im Beispielprogramm const intbestimmen die Autoren den Abstand (Leerzeichen) zwischen der Begrüßung und den Sternchen. Anschließend bitten sie den Leser im Rahmen der Übung, den Benutzer um Eingabe zu bitten, wie groß die Polsterung sein soll.

All dies scheint einfach zu sein. Ich frage den Benutzer nach zwei Ganzzahlen ( int), speichere sie und ändere das Programm, um diese Ganzzahlen zu verwenden. Dabei entferne ich die vom Autor verwendeten Ganzzahlen, obwohl ich die folgende Warnung erhalte.

Übung 2-3.cpp: 46: Warnung: Vergleich zwischen vorzeichenbehafteten und vorzeichenlosen ganzzahligen Ausdrücken

Nach einigen Recherchen scheint dies daran zu liegen, dass der Code versucht, eine der oben genannten Ganzzahlen ( int) mit a zu vergleichen string::size_type, was in Ordnung ist. Aber ich habe mich gefragt - bedeutet das, dass ich eine der ganzen Zahlen ändern sollte unsigned int? Ist es wichtig, explizit anzugeben, ob meine Ganzzahlen signiert oder nicht signiert 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 aufgeführt, die cvom Typ sind, string::size_typeda wir nicht wissen, wie lange die Begrüßung dauern könnte - aber warum bekomme ich dieses Problem jetzt, wenn der Code des Autors das Problem bei der Verwendung nicht bekommen hat const int? Wird dies später in diesem Buch erklärt - für alle, die möglicherweise Accelerated C ++ abgeschlossen haben ?

Ich bin auf Linux Mint und benutze g ++ über Geany, wenn das hilft oder einen Unterschied macht (wie ich gelesen habe, könnte es bei der Bestimmung, was string::size_typeist).

Tim Harrington
quelle
2
würde man nicht annehmen, dass man sowieso nicht signierte Ints haben möchte? Ich kann mir keinen logischen Grund
vorstellen,
Dies ist wahr und ich habe dies 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 verwendeten. Ich bin mir sicher, dass ich in dem Buch darauf zurückkommen werde, aber ich kann nicht anders, als neugierig zu sein.
Tim Harrington
Verschrotte das - offensichtlich gab es in dieser Situation keine Warnung, weil der int immer 1 ... oops sein würde.
Tim Harrington
1
Im Allgemeinen lohnt sich die Erhöhung der Reichweite nicht für die Verwendung unsignedintegraler Typen für Zählungen. Nicht signierte Nummern haben auch ein garantiertes Umlaufverhalten, wodurch sie geringfügig weniger effizient sind.
Jon Purdy
4
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.
Kristopher Johnson

Antworten:

101

Es ist in der Regel eine gute Idee zu erklären Variablen wie unsignedoder size_tob sie auf Größen verglichen werden, um dieses Problem zu vermeiden. Verwenden Sie nach Möglichkeit den genauen Typ, mit dem Sie vergleichen möchten (z. B. std::string::size_typebeim Vergleich mit std::stringder Länge von a).

Compiler geben Warnungen zum Vergleichen von vorzeichenbehafteten und vorzeichenlosen Typen aus, da die Bereiche für vorzeichenbehaftete und vorzeichenlose Ints unterschiedlich sind. 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. Zum Beispiel:

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();
}
Kristopher Johnson
quelle
11
Ich weiß, dass der gegenwärtige C-Standard manchmal verlangt, dass negativ vorzeichenbehaftete Werte größer sind als vorzeichenlose Werte, aber sollten Situationen, in denen dies auftritt, nicht als veraltet angesehen werden? Ich möchte die Standards zumindest entwickeln , um zu sehen erlauben Compiler arithmetisch-korrektes Verhalten zu erzeugen (was bedeutet , dass , wenn der Wert mit Vorzeichen negativ ist, vergleicht er kleiner, und wenn der Wert ohne Vorzeichen den maximalen Wert des signierten Typs überschreitet, vergleicht sie größer ). Es scheint seltsam, dass Compiler ohne explizite Typecasts ein doofes Verhalten erzeugen müssen.
Supercat
4
@supercat: Da Ganzzahlvergleiche zu einer einzelnen Maschinenanweisung kompiliert werden und für Tests oder Edge-Case-Handhabung mehrere Maschinenanweisungen erforderlich sind, wird das, was Sie vorschlagen, wahrscheinlich nicht als C-Feature hinzugefügt ... es könnte sicherlich nicht das sein Standardverhalten, da es die Leistung unnötig beeinträchtigen würde, selbst wenn der Programmierer weiß, dass dies nicht erforderlich ist.
Blake Miller
@BlakeMiller: Code, der einen vorzeichenbehafteten und einen vorzeichenlosen Wert vergleichen möchte, als ob beide vorzeichenlos wären, könnte einen umwandeln und "volle Geschwindigkeit" ausführen. Andernfalls würde in vielen Fällen der Unterschied zwischen einem Vergleichen und Springen mit zwei Anweisungen gegenüber drei bestehen, was billiger wäre als Code, der die verschiedenen Fälle manuell behandelt.
Supercat
1
@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 anordnen, dass vor dem Vergleich Das "Vorzeichen" -Flag würde das obere Bit eines der Operanden enthalten, so dass ein einziger bedingter Sprung vor dem Vergleich ausreichen würde, um eine korrekte Semantik sicherzustellen. Beachten Sie, dass ein Compiler, da es verschiedene Möglichkeiten gibt, eine korrekte Semantik zu erzielen, diejenige auswählen kann, die am billigsten ist. Das Schreiben von C-Code für die korrekte Semantik wäre schwieriger.
Supercat
6
Um zu demonstrieren, dass "die Ergebnisse überraschend sein können", gibt das folgende Programm (nach dem Einfügen #include <cstdio>oben ... und ich verwende g ++ 4.4.7) "true" aus und gibt an, dass es wahr ist, dass (signiert) -1 ist größer als (ohne Vorzeichen) 12: int main(int, char**) { int x = -1; unsigned int y = 12; printf("x > y: %s\n", x > y ? "true":"false"); return 0; }
villapx
9

Ich hatte gestern genau das gleiche Problem, als ich Problem 2-3 in Accelerated C ++ durcharbeitete. Der Schlüssel besteht darin, alle Variablen, die Sie vergleichen (unter Verwendung von Booleschen Operatoren), in kompatible Typen zu ändern. In diesem Fall bedeutet das string::size_type(oder unsigned int, aber da in diesem Beispiel das erstere verwendet wird, 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 betont haben.

Was dieses Beispiel komplizierter macht, ist, dass die verschiedenen Füllvariablen (Padsides und Padtopbottom) sowie alle Zähler ebenfalls geändert werden müssen string::size_type.

Wenn Sie zu Ihrem Beispiel kommen, sieht der von Ihnen veröffentlichte Code folgendermaßen aus:

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 string::size_typein der forSchleife initialisieren würden . Sie müssen die for-Schleife also folgendermaßen initialisieren:

    for (string::size_type r=0; r!=rows; ++r)   //If r and rows are string::size_type, no error!

Wenn Sie also eine string::size_typeVariable in den Mix einfügen, müssen alle Operanden einen kompatiblen Typ haben, damit sie ohne Warnungen kompiliert werden können, wenn Sie eine boolesche Operation für dieses Element ausführen möchten.

Eric
quelle
6

Der wichtige Unterschied zwischen vorzeichenbehafteten und vorzeichenlosen Ints ist die Interpretation des letzten Bits. Das letzte Bit in vorzeichenbehafteten Typen stellt das Vorzeichen der Zahl dar, dh: z.

0001 ist 1 signiert und nicht signiert 1001 ist -1 signiert und 9 nicht signiert

(Ich habe das gesamte Komplementproblem aus Gründen der Klarheit der Erklärung vermieden! So werden Ints nicht genau im Speicher dargestellt!)

Sie können sich vorstellen, dass es einen Unterschied macht, zu wissen, ob Sie mit -1 oder mit +9 vergleichen. In vielen Fällen sind Programmierer einfach zu faul, um das Zählen von Ints als vorzeichenlos zu deklarieren (Aufblähen des for-Loop-Head-Fi). Dies ist normalerweise kein Problem, da Sie bei Ints bis 2 ^ 31 zählen müssen, bis Ihr Vorzeichen Sie beißt. Deshalb ist es nur eine Warnung. Weil wir zu faul sind, um 'unsigned' anstelle von 'int' zu schreiben.

AndreasT
quelle
Ah, ich verstehe - ich habe jetzt die Zählung int als nicht signiert geändert. Wird dies als gute oder sogar als schlechte Praxis angesehen? :)
Tim Harrington
Wenn Sie abstimmen, erklären Sie bitte kurz, warum. Auch wenn es nur ein Wort ist. Ich kann nichts falsches mit meiner Antwort sehen. Mit welchem ​​Problem können Sie mir helfen?
AndreasT
1
@ Tim: "unsigned" ist ein Synonym für "unsigned int". Sie sollten unsigned int oder den stl-Standardzähl- / iterierenden Variablentyp std :: size_t (der auch ein Synonym ist) verwenden. Es wird empfohlen, in allen Fällen von "über Elemente 0 bis n iterieren" ohne Vorzeichen zu verwenden. Es verbessert die Klarheit und entfernt Warnungen, so dass es ein Gewinner ist ;-)
AndreasT
8
Die interne Darstellung von Ganzzahlen mit Vorzeichen ist vom Compiler (dh von der Maschine) abhängig. Ihre Notation mit einem Vorzeichenbit wird aufgrund einiger Probleme nicht häufig verwendet (+/- Null ist eines davon). Die meisten Maschinen verwenden einen Zweierkomplementbegriff, um negative Zahlen darzustellen. Der Vorteil ist, dass auch normale (vorzeichenlose) Arithmetik ohne Änderungen verwendet werden kann. -1 in 2s Komplementbegriff wäre übrigens 1111.
Sstn
1
@AndreasT: Während 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. 1001für -1 war eine schlechte Wahl, eine viel bessere Wahl wäre " 1111gleich -1 signiert und 15 nicht signiert"
MestreLion
4

In den extremen Bereichen kann ein vorzeichenloses int 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 in denselben Typ umwandeln, damit die Warnung verschwindet (verwenden Sie C ++ - Umwandlung, damit sie leicht zu erkennen sind).

Alternativ können Sie die Variablen auf denselben Typ festlegen, um zu verhindern, dass sich der Compiler beschwert.
Ich meine, ist es möglich, eine negative Polsterung zu haben? Wenn ja, dann behalten Sie 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.

Martin York
quelle
0

oder verwenden Sie diese Header-Bibliothek und schreiben Sie:

// |notEqaul|less|lessEqual|greater|greaterEqual
if(sweet::equal(valueA,valueB))

und kümmere dich nicht um signierte / nicht signierte oder andere Größen

Brenner
quelle
0

Das Hauptproblem besteht darin, dass die zugrunde liegende Hardware, die CPU, nur Anweisungen zum Vergleichen von zwei vorzeichenbehafteten Werten oder von zwei vorzeichenlosen Werten enthält. Wenn Sie der vorzeichenlosen Vergleichsanweisung einen vorzeichenbehafteten, negativen Wert übergeben, wird dieser als große positive Zahl behandelt. Also wird -1, das Bitmuster mit allen eingeschalteten Bits (Zweierkomplement), der maximale vorzeichenlose Wert für die gleiche Anzahl von Bits.

8-Bit: -1 vorzeichenbehaftet sind die gleichen Bits wie 255 vorzeichenlose 16-Bit: -1 vorzeichenbehaftet sind die gleichen Bits wie 65535 vorzeichenlos usw.

Wenn Sie also den folgenden Code haben:

int fd;
fd = open( .... );

int cnt;
SomeType buf;

cnt = read( fd, &buf, sizeof(buf) );

if( cnt < sizeof(buf) ) {
    perror("read error");
}

Sie werden feststellen, dass cnt auf -1 gesetzt wird, wenn der Aufruf von read (2) fehlschlägt, weil der Dateideskriptor ungültig wird (oder ein anderer Fehler vorliegt). Beim Vergleich mit sizeof (buf), einem vorzeichenlosen Wert, ist die if () - Anweisung falsch, da 0xffffffff nicht kleiner als sizeof () einer (vernünftigen, nicht als maximale Größe zusammengestellten) Datenstruktur ist.

Daher müssen Sie das obige if schreiben, um die signierte / nicht signierte Warnung wie folgt zu entfernen:

if( cnt < 0 || (size_t)cnt < sizeof(buf) ) {
    perror("read error");
}

Dies spricht nur laut zu den Problemen.

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 signierten Werttyp finden, der funktioniert, verwenden Sie einen zu kleinen Prozessor oder eine zu große Anzahl von Werten in der Sprache Ihrer Wahl. Wenn wie beim Geld jede Ziffer zählt, gibt es Systeme, die in den meisten Sprachen verwendet werden können und die Ihnen eine unendliche Genauigkeit bieten. C / C ++ macht das einfach nicht gut, und Sie müssen sehr explizit über alles rund um Typen sein, wie in vielen anderen Antworten hier erwähnt.

Gregg Wunderbar
quelle