Einige der Antworten hier erwähnen die überraschenden Heraufstufungsregeln zwischen vorzeichenbehafteten und vorzeichenlosen Werten, aber dies scheint eher ein Problem beim Mischen von vorzeichenbehafteten und vorzeichenlosen Werten zu sein und erklärt nicht unbedingt, warum vorzeichenbehaftete Variablen außerhalb von Mischungsszenarien gegenüber vorzeichenlosen bevorzugt werden.
Nach meiner Erfahrung gibt es außerhalb gemischter Vergleiche und Beförderungsregeln zwei Hauptgründe, warum vorzeichenlose Werte Fehlermagnete sind:
Vorzeichenlose Werte haben eine Diskontinuität bei Null, dem häufigsten Wert bei der Programmierung
Sowohl vorzeichenlose als auch vorzeichenbehaftete Ganzzahlen weisen bei ihren Minimal- und Maximalwerten Diskontinuitäten auf, bei denen sie umlaufen (vorzeichenlos) oder undefiniertes Verhalten verursachen (vorzeichenbehaftet). Für unsigned
diese Punkte sind bei Null und UINT_MAX
. Denn int
sie sind bei INT_MIN
und INT_MAX
. Typische Werte von INT_MIN
und INT_MAX
auf einem System mit 4-Byte- int
Werten sind -2^31
und 2^31-1
, und auf einem solchen System UINT_MAX
ist typischerweise 2^32-1
.
Das Hauptproblem unsigned
, das Fehler verursacht , besteht darin, int
dass es eine Diskontinuität bei Null aufweist . Null ist natürlich ein sehr häufiger Wert in Programmen, zusammen mit anderen kleinen Werten wie 1,2,3. Es ist üblich, kleine Werte, insbesondere 1, in verschiedenen Konstrukten zu addieren und zu subtrahieren. Wenn Sie etwas von einem unsigned
Wert subtrahieren und dieser zufällig Null ist, erhalten Sie nur einen massiven positiven Wert und einen fast sicheren Fehler.
Betrachten Sie Code-Iterationen über alle Werte in einem Vektor nach Index mit Ausnahme der letzten 0,5 :
for (size_t i = 0; i < v.size() - 1; i++) {
Dies funktioniert einwandfrei, bis Sie eines Tages einen leeren Vektor übergeben. Anstatt null Iterationen durchzuführen, erhalten Sie v.size() - 1 == a giant number
1 und 4 Milliarden Iterationen und haben fast eine Pufferüberlauf-Sicherheitsanfälligkeit.
Sie müssen es so schreiben:
for (size_t i = 0; i + 1 < v.size(); i++) {
So kann es in diesem Fall "behoben" werden, aber nur durch sorgfältiges Nachdenken über die unsignierte Natur von size_t
. Manchmal können Sie den obigen Fix nicht anwenden, weil Sie anstelle eines konstanten einen variablen Offset haben, den Sie anwenden möchten, der positiv oder negativ sein kann. Welche "Seite" des Vergleichs Sie also anwenden müssen, hängt von der Signatur ab - Jetzt wird der Code wirklich chaotisch.
Es gibt ein ähnliches Problem mit Code, der versucht, bis einschließlich Null zu iterieren. So etwas while (index-- > 0)
funktioniert gut, aber das scheinbar Äquivalent while (--index >= 0)
wird niemals für einen vorzeichenlosen Wert beendet. Ihr Compiler warnt Sie möglicherweise, wenn die rechte Seite buchstäblich Null ist, aber sicherlich nicht, wenn es sich um einen zur Laufzeit ermittelten Wert handelt.
Kontrapunkt
Einige könnten argumentieren, dass signierte Werte auch zwei Diskontinuitäten aufweisen. Warum also nicht signierte auswählen? Der Unterschied besteht darin, dass beide Diskontinuitäten sehr (maximal) weit von Null entfernt sind. Ich halte dies wirklich für ein separates Problem des "Überlaufs". Sowohl vorzeichenbehaftete als auch vorzeichenlose Werte können bei sehr großen Werten überlaufen. In vielen Fällen ist ein Überlauf aufgrund von Einschränkungen des möglichen Wertebereichs nicht möglich, und ein Überlauf vieler 64-Bit-Werte kann physikalisch unmöglich sein. Selbst wenn dies möglich ist, ist die Wahrscheinlichkeit eines Fehlers im Zusammenhang mit einem Überlauf im Vergleich zu einem Fehler "bei Null" häufig gering, und ein Überlauf tritt auch bei vorzeichenlosen Werten auf . So unsigned kombiniert das Schlimmste aus beiden Welten: potenzieller Überlauf mit sehr großen Größenwerten und eine Diskontinuität bei Null. Signiert hat nur der erstere.
Viele werden argumentieren, "Sie verlieren ein bisschen" mit unsigniert. Dies ist häufig der Fall - aber nicht immer (wenn Sie Unterschiede zwischen vorzeichenlosen Werten darstellen müssen, verlieren Sie dieses Bit sowieso: So viele 32-Bit-Dinge sind sowieso auf 2 GiB beschränkt, oder Sie haben eine seltsame Grauzone, in der es heißt Eine Datei kann 4 GiB groß sein, aber Sie können bestimmte APIs für die zweite Hälfte von 2 GiB nicht verwenden.
Selbst in den Fällen, in denen unsignierte Sie ein bisschen kaufen: Es kauft Ihnen nicht viel: Wenn Sie mehr als 2 Milliarden "Dinge" unterstützen müssten, müssten Sie wahrscheinlich bald mehr als 4 Milliarden unterstützen.
Vorzeichenlose Werte sind logischerweise eine Teilmenge vorzeichenbehafteter Werte
Mathematisch gesehen sind vorzeichenlose Werte (nicht negative Ganzzahlen) eine Teilmenge vorzeichenbehafteter Ganzzahlen (nur _integers genannt). 2 . Doch unterzeichnet natürlich Werte Pop aus Operationen ausschließlich auf unsigned Werte wie Subtraktion. Wir könnten sagen, dass vorzeichenlose Werte nicht unter Subtraktion geschlossen werden . Gleiches gilt nicht für vorzeichenbehaftete Werte.
Möchten Sie das "Delta" zwischen zwei vorzeichenlosen Indizes in einer Datei finden? Nun, Sie sollten die Subtraktion besser in der richtigen Reihenfolge durchführen, sonst erhalten Sie die falsche Antwort. Natürlich benötigen Sie häufig eine Laufzeitprüfung, um die richtige Reihenfolge zu ermitteln! Wenn Sie vorzeichenlose Werte als Zahlen behandeln, werden Sie häufig feststellen, dass (logisch) signierte Werte sowieso immer wieder angezeigt werden. Sie können also genauso gut mit signierten Werten beginnen.
Kontrapunkt
Wie in Fußnote (2) oben erwähnt, sind vorzeichenbehaftete Werte in C ++ keine Teilmenge von vorzeichenlosen Werten derselben Größe, sodass vorzeichenlose Werte dieselbe Anzahl von Ergebnissen darstellen können wie vorzeichenbehaftete Werte.
Stimmt, aber der Bereich ist weniger nützlich. Betrachten Sie die Subtraktion und vorzeichenlose Zahlen mit einem Bereich von 0 bis 2N und vorzeichenbehaftete Zahlen mit einem Bereich von -N bis N. Beliebige Subtraktionen führen in beiden Fällen zu Ergebnissen im Bereich von -2N bis 2N, und beide Arten von Ganzzahlen können nur darstellen die Hälfte. Nun, es stellt sich heraus, dass der Bereich, der um Null von -N bis N zentriert ist, normalerweise viel nützlicher ist (enthält mehr tatsächliche Ergebnisse im Code der realen Welt) als der Bereich 0 bis 2N. Betrachten Sie eine andere typische Verteilung als die einheitliche (log, zipfian, normal, was auch immer) und ziehen Sie in Betracht, zufällig ausgewählte Werte von dieser Verteilung zu subtrahieren: Viel mehr Werte enden in [-N, N] als in [0, 2N] (tatsächlich resultierende Verteilung ist immer auf Null zentriert).
64-Bit schließt die Tür aus vielen Gründen, vorzeichenbehaftete Werte als Zahlen zu verwenden
Ich denke , die Argumente oben bereits überzeugend waren für 32-Bit - Werte, aber die Überlauffälle, die bei verschiedenen Schwellenwerten sowohl mit und ohne Vorzeichen beeinflussen, kann auftreten , für 32-Bit - Werte, da „2000000000“ eine Zahl , die von vielen überschritten können abstrakte und physikalische Größen (Milliarden von Dollar, Milliarden von Nanosekunden, Arrays mit Milliarden von Elementen). Wenn also jemand genug von der Verdoppelung des positiven Bereichs für vorzeichenlose Werte überzeugt ist, kann er den Fall vertreten, dass ein Überlauf eine Rolle spielt und vorzeichenlos etwas bevorzugt.
Außerhalb spezialisierter Domänen beseitigen 64-Bit-Werte dieses Problem weitgehend. Vorzeichenbehaftete 64-Bit-Werte haben einen oberen Bereich von 9.223.372.036.854.775.807 - mehr als neun Billionen . Das sind viele Nanosekunden (ungefähr 292 Jahre) und viel Geld. Es ist auch ein größeres Array, als jeder Computer wahrscheinlich lange Zeit RAM in einem kohärenten Adressraum hat. Vielleicht reichen 9 Billionen für alle (vorerst)?
Wann werden vorzeichenlose Werte verwendet?
Beachten Sie, dass der Styleguide die Verwendung von Zahlen ohne Vorzeichen nicht verbietet oder sogar unbedingt davon abhält. Es schließt mit:
Verwenden Sie keinen vorzeichenlosen Typ, um lediglich zu behaupten, dass eine Variable nicht negativ ist.
In der Tat gibt es gute Verwendungsmöglichkeiten für vorzeichenlose Variablen:
Wenn Sie eine N-Bit-Menge nicht als Ganzzahl, sondern einfach als "Beutel mit Bits" behandeln möchten. Zum Beispiel als Bitmaske oder Bitmap oder als N-Boolesche Werte oder was auch immer. Diese Verwendung geht oft Hand in Hand mit den Typen mit fester Breite wie uint32_t
und uint64_t
da Sie häufig die genaue Größe der Variablen wissen möchten. Ein Hinweis darauf , dass eine bestimmte Variable diese Behandlung verdient , ist , dass man nur auf sich mit den arbeitet bitweise Operatoren wie ~
, |
, &
, ^
, >>
und so weiter, und nicht mit den arithmetischen Operationen wie +
, -
, *
, /
usw.
Unsigned ist hier ideal, da das Verhalten der bitweisen Operatoren genau definiert und standardisiert ist. Vorzeichenbehaftete Werte weisen verschiedene Probleme auf, z. B. undefiniertes und nicht angegebenes Verhalten beim Verschieben und eine nicht angegebene Darstellung.
Wenn Sie tatsächlich modulare Arithmetik wollen. Manchmal möchten Sie tatsächlich 2 ^ N modulare Arithmetik. In diesen Fällen ist "Überlauf" eine Funktion, kein Fehler. Vorzeichenlose Werte geben Ihnen hier das, was Sie wollen, da sie für die Verwendung modularer Arithmetik definiert sind. Vorzeichenbehaftete Werte können überhaupt nicht (einfach, effizient) verwendet werden, da sie eine nicht spezifizierte Darstellung haben und der Überlauf undefiniert ist.
0.5 Nachdem ich das geschrieben hatte, wurde mir klar, dass dies fast identisch mit Jarods Beispiel ist , das ich nicht gesehen hatte - und aus gutem Grund ist es ein gutes Beispiel!
1 Wir sprechen size_t
hier also normalerweise von 2 ^ 32-1 auf einem 32-Bit-System oder 2 ^ 64-1 auf einem 64-Bit-System.
2 In C ++ ist dies nicht genau der Fall, da vorzeichenlose Werte am oberen Ende mehr Werte enthalten als der entsprechende vorzeichenbehaftete Typ. Es besteht jedoch das Grundproblem, dass die Bearbeitung vorzeichenloser Werte zu (logisch) vorzeichenbehafteten Werten führen kann, aber es gibt kein entsprechendes Problem mit vorzeichenbehafteten Werten (da vorzeichenbehaftete Werte bereits vorzeichenlose Werte enthalten).
unsigned int x = 0; --x;
und zu sehen, wasx
wird. Ohne Grenzwertprüfungen könnte die Größe plötzlich einen unerwarteten Wert erhalten, der leicht zu UB führen könnte.