Ist es wahrscheinlicher, dass die Verwendung eines nicht signierten als eines signierten Int Fehler verursacht? Warum?

81

Im Google C ++ Style Guide zum Thema "Ganzzahlen ohne Vorzeichen" wird dies empfohlen

Aufgrund eines historischen Unfalls verwendet der C ++ - Standard auch vorzeichenlose Ganzzahlen, um die Größe von Containern darzustellen. Viele Mitglieder des Standardkörpers halten dies für einen Fehler, aber es ist derzeit praktisch unmöglich, ihn zu beheben. Die Tatsache, dass vorzeichenlose Arithmetik nicht das Verhalten einer einfachen Ganzzahl modelliert, sondern durch den Standard zur Modellierung modularer Arithmetik (Umlaufen bei Überlauf / Unterlauf) definiert wird, bedeutet, dass eine signifikante Klasse von Fehlern vom Compiler nicht diagnostiziert werden kann.

Was ist falsch an modularer Arithmetik? Ist das nicht das erwartete Verhalten eines Int ohne Vorzeichen?

Auf welche Art von Fehlern (eine bedeutende Klasse) bezieht sich der Leitfaden? Überlaufende Bugs?

Verwenden Sie keinen vorzeichenlosen Typ, um lediglich zu behaupten, dass eine Variable nicht negativ ist.

Ein Grund, warum ich mir vorstellen kann, signiertes int anstelle von nicht signiertem int zu verwenden, ist, dass es leichter zu erkennen ist, wenn es überläuft (zu negativ).

user7586189
quelle
4
Versuchen Sie zu tun unsigned int x = 0; --x;und zu sehen, was xwird. Ohne Grenzwertprüfungen könnte die Größe plötzlich einen unerwarteten Wert erhalten, der leicht zu UB führen könnte.
Einige Programmierer Typ
33
Zumindest ein vorzeichenloser Überlauf hat ein genau definiertes Verhalten und führt zu erwarteten Ergebnissen.
user7860670
35
Wenn Sie ein wenig suchen (ohne Ihre Frage, aber nicht mit Google Styleguides), werden Sie (manchmal zu Recht) Kritik an den Google Styleguides finden. Nimm sie nicht als Evangelium.
Einige Programmierer Typ
18
Auf der anderen Seite, intÜberlauf und Unterlauf sind UB. Es ist weniger wahrscheinlich, dass eine Situation intauftritt, in der versucht wird, einen Wert auszudrücken, der nicht möglich ist, als eine Situation, in der ein Wert unsigned intunter Null verringert wird. Die Art von Personen, die vom Verhalten der unsigned intArithmetik überrascht wären, ist jedoch die Art von Personen, die dies auch könnten Schreiben Sie Code, der einen intüberlaufbedingten UB verursachen würde, wie a < a + 1z. B. die Überprüfung auf Überlauf.
François Andrieux
12
Wenn vorzeichenlose Ganzzahlen überlaufen, ist dies gut definiert. Wenn vorzeichenbehaftete Ganzzahlen überlaufen, handelt es sich um undefiniertes Verhalten. Ich bevorzuge ein genau definiertes Verhalten, aber wenn Ihr Code nicht mit übergelaufenen Werten umgehen kann, sind Sie mit beiden verloren. Der Unterschied ist: Für signiert sind Sie bereits für den Überlaufvorgang verloren, für nicht signiert im folgenden Code. Der einzige Punkt, dem ich zustimme, ist, dass ein vorzeichenloser Ganzzahltyp die falsche Wahl ist, wenn Sie negative Werte benötigen - offensichtlich.
zu ehrlich für diese Seite

Antworten:

70

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 unsigneddiese Punkte sind bei Null und UINT_MAX. Denn intsie sind bei INT_MINund INT_MAX. Typische Werte von INT_MINund INT_MAXauf einem System mit 4-Byte- intWerten sind -2^31und 2^31-1, und auf einem solchen System UINT_MAXist typischerweise 2^32-1.

Das Hauptproblem unsigned, das Fehler verursacht , besteht darin, intdass 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 unsignedWert 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++) { // do something }

Dies funktioniert einwandfrei, bis Sie eines Tages einen leeren Vektor übergeben. Anstatt null Iterationen durchzuführen, erhalten Sie v.size() - 1 == a giant number1 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++) { // do something }

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_tund uint64_tda 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_thier 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).

BeeOnRope
quelle
10
Ich stimme mit allem überein, was Sie gepostet haben, aber "64 Bit sollten für alle ausreichen" scheint sicher viel zu nahe an "640k sollten für alle reichen" zu sein.
Andrew Henle
6
@ Andrew - yup, ich habe meine Worte sorgfältig ausgewählt :).
BeeOnRope
4
"64-Bit schließt die Tür bei vorzeichenlosen Werten" -> Nicht einverstanden. Einige ganzzahlige Programmieraufgaben sind einfach, kein Fall des Zählens und erfordern keine negativen Werte, benötigen jedoch Potenz-2-Breiten: Passwörter, Verschlüsselung, Bitgrafiken, Vorteile mit vorzeichenloser Mathematik. Viele Ideen hier zeigen auf, warum Code signierte Mathematik verwenden könnte, wenn dies möglich ist, aber nicht signierte Typen unbrauchbar macht und die Tür zu ihnen schließt.
chux
2
@Deduplicator - ja, ich habe es weggelassen, da es mehr oder weniger wie ein Unentschieden wirkt. Auf der Seite des vorzeichenlosen Mod-2 ^ N-Wraparounds haben Sie zumindest ein definiertes Verhalten und es treten keine unerwarteten "Optimierungen" auf. Auf der Seite von UB ist ein Überlauf während der Arithmetik auf vorzeichenlos oder signiert wahrscheinlich ein Fehler in der überwiegenden Mehrheit von Fällen (außerhalb der wenigen, die Mod-Arithmetik erwarten) und Compiler bieten Optionen wie diese -ftrapv, die alle vorzeichenbehafteten Überläufe, aber nicht alle vorzeichenlosen Überläufe abfangen können. Die Auswirkungen auf die Leistung sind nicht allzu schlecht, daher kann es -ftrapvin einigen Szenarien sinnvoll sein, damit zu kompilieren .
BeeOnRope
2
@BeeOnRope That's about the age of the universe measured in nanoseconds.Das bezweifle ich. Das Universum ist ungefähr 13.7*10^9 yearsalt, was ist 4.32*10^17 soder 4.32*10^26 ns. Um 4.32*10^26als int darzustellen , benötigen Sie mindestens 90 bits. 9,223,372,036,854,775,807 nswäre nur etwa 292.5 years.
Osiris
36

Wie bereits erwähnt, Mischen unsignedund signedkönnte zu einem unerwarteten Verhalten führt (auch wenn sie gut definiert).

Angenommen, Sie möchten alle Elemente des Vektors mit Ausnahme der letzten fünf durchlaufen, dann schreiben Sie möglicherweise falsch:

for (int i = 0; i < v.size() - 5; ++i) { foo(v[i]); } // Incorrect
// for (int i = 0; i + 5 < v.size(); ++i) { foo(v[i]); } // Correct

Nehmen wir v.size() < 5dann an, wie es v.size()ist unsigned, s.size() - 5wäre es eine sehr große Zahl, und so i < v.size() - 5wäre es truefür einen mehr erwarteten Wertebereich von i. Und UB passiert dann schnell (einmal ohne gebundenen Zugriff i >= v.size())

Wenn ein v.size()vorzeichenbehafteter Wert zurückgegeben worden wäre, s.size() - 5wäre er negativ gewesen, und im obigen Fall wäre die Bedingung sofort falsch.

Auf der anderen Seite sollte der Index zwischen liegen [0; v.size()[, unsignedwas Sinn macht. Signed hat auch ein eigenes Problem als UB mit Überlauf oder implementierungsdefiniertem Verhalten für die Rechtsverschiebung einer negativ signierten Zahl, aber weniger häufige Fehlerquelle für die Iteration.

Jarod42
quelle
2
Obwohl ich selbst signierte Zahlen verwende, wann immer ich kann, denke ich nicht, dass dieses Beispiel stark genug ist. Jemand, der lange Zeit vorzeichenlose Zahlen verwendet, kennt diese Redewendung sicherlich: Stattdessen i<size()-Xsollte man schreiben i+X<size(). Sicher, es ist eine Sache, an die man sich erinnert, aber meiner Meinung nach ist es nicht so schwer, sich daran zu gewöhnen.
Geza
8
Was Sie sagen, ist im Grunde, dass man die Sprache und die Zwangsregeln zwischen den Typen kennen muss. Ich sehe nicht, wie sich dies ändert, ob man signierte oder nicht signierte verwendet, wie die Frage stellt. Nicht, dass ich die Verwendung von signiert überhaupt empfehlen würde, wenn keine negativen Werte erforderlich sind. Ich bin mit @geza einverstanden, benutze nur signiert wenn nötig. Dies macht den Google Guide bestenfalls fragwürdig . Imo ist es ein schlechter Rat.
zu ehrlich für diese Seite
2
@toohonestforthissite Der Punkt ist, dass die Regeln arkan, still und Hauptursachen für Fehler sind. Die Verwendung ausschließlich signierter Typen für die Arithmetik entlastet Sie von dem Problem. Übrigens ist die Verwendung von nicht signierten Typen zur Durchsetzung positiver Werte einer der schlimmsten Missbräuche für sie.
Passant Bis zum
2
Zum Glück geben moderne Compiler und IDEs Warnungen aus, wenn vorzeichenbehaftete und vorzeichenlose Zahlen in einem Ausdruck gemischt werden.
Alexey B.
5
@PasserBy: Wenn Sie sie als arkan bezeichnen, müssen Sie die Ganzzahl-Promotions und die UB für den Überlauf von vorzeichenbehafteten arkanen Typen ebenfalls hinzufügen. Und der sehr gebräuchliche Operator sizeof gibt ohnehin ein vorzeichenloses zurück, sodass Sie über sie Bescheid wissen müssen. Sagte: Wenn Sie die Sprachdetails nicht lernen möchten, verwenden Sie einfach nicht C oder C ++! Wenn man bedenkt, dass Google für go wirbt, ist das vielleicht genau ihr Ziel. Die Zeiten von "Sei nicht böse" sind lange vorbei ...
zu ehrlich für diese Seite
20

Eines der haarsträubendsten Beispiele für einen Fehler ist, wenn Sie signierte und nicht signierte MIX-Werte verwenden:

#include <iostream>
int main()  {
    auto qualifier = -1 < 1u ? "makes" : "does not make";
    std::cout << "The world " << qualifier << " sense" << std::endl;
}

Die Ausgabe:

Die Welt macht keinen Sinn

Wenn Sie keine triviale Anwendung haben, ist es unvermeidlich, dass Sie entweder gefährliche Mischungen zwischen vorzeichenbehafteten und nicht vorzeichenbehafteten Werten erhalten (was zu Laufzeitfehlern führt), oder wenn Sie Warnungen auslösen und sie zur Kompilierungszeit fehlerhaft machen, erhalten Sie eine Menge static_casts in Ihrem Code. Aus diesem Grund ist es am besten, vorzeichenbehaftete Ganzzahlen für Typen für mathematische oder logische Vergleiche zu verwenden. Verwenden Sie nur vorzeichenlose Bitmasken und Typen, die Bits darstellen.

Das Modellieren eines Typs ohne Vorzeichen basierend auf der erwarteten Domäne der Werte Ihrer Zahlen ist eine schlechte Idee. Die meisten Zahlen liegen näher bei 0 als bei 2 Milliarden. Bei vorzeichenlosen Typen liegen viele Ihrer Werte näher am Rand des gültigen Bereichs. Um alles noch schlimmer zu machen, das letzte kann Wert in einem bekannten positiven Bereich, aber während Auswerten von Ausdrücken können Zwischenwerte unterlaufen, und wenn sie in Zwischenform verwendet werden können sehr falsche Werte. Schließlich, auch wenn Ihre Werte immer positiv zu erwarten sind, die nicht das bedeutet sie werden nicht interact mit anderen Variablen, kann negativ sein, und so können Sie mit einer Zwangssituation mit und ohne Vorzeichen Typen am Ende Mischen, das ist der schlechteste Ort zu sein.

Chris Uzdavinis
quelle
8
Das Modellieren eines Typs, der nicht signiert werden soll, basierend auf der erwarteten Domäne der Werte Ihrer Zahlen ist eine schlechte Idee *, wenn Sie implizite Konvertierungen nicht als Warnungen behandeln und zu faul sind, um geeignete Typumwandlungen zu verwenden. * Modellieren Sie Ihre Typen anhand ihrer erwarteten Gültigkeit Werte sind völlig vernünftig, nur nicht in C / C ++ mit integrierten Typen.
Villasv
1
@ user7586189 Es wird empfohlen, die Instanziierung ungültiger Daten unmöglich zu machen. Daher ist es durchaus sinnvoll, nur positive Variablen für Größen zu haben. Sie können die in C / C ++ integrierten Typen jedoch nicht so einstellen, dass standardmäßig schlechte Casts wie in dieser Antwort nicht zugelassen werden, und die Gültigkeit liegt letztendlich in der Verantwortung eines anderen. Wenn Sie in einer Sprache mit strengeren Besetzungen (auch zwischen integrierten Funktionen) arbeiten, ist die Modellierung der erwarteten Domäne eine ziemlich gute Idee.
Villasv
1
Beachten Sie , ich habe erwähnen Warnungen kurbelt und sie zu Fehlern einstellen, aber nicht jeder tut. Ich stimme @villasv immer noch nicht mit Ihrer Aussage über die Modellierung von Werten überein. Wenn Sie unsigned wählen, modellieren Sie AUCH implizit jeden anderen Wert, mit dem es in Kontakt kommen kann, ohne viel Voraussicht darüber zu haben, was das sein wird. Und mit ziemlicher Sicherheit falsch verstanden.
Chris Uzdavinis
1
Das Modellieren unter Berücksichtigung der Domäne ist eine gute Sache. Die Verwendung von unsigned zum Modellieren der Domain ist NICHT. (Signiert gegen nicht signiert sollte basierend auf den Verwendungsarten und nicht auf dem Wertebereich ausgewählt werden , es sei denn, es ist unmöglich, etwas anderes zu tun.)
Chris Uzdavinis
2
Sobald Ihre Codebasis eine Mischung aus vorzeichenbehafteten und vorzeichenlosen Werten enthält und Sie Warnungen aufdecken und zu Fehlern heraufstufen, wird der Code mit static_casts übersät, um die Konvertierungen explizit zu machen (da die Mathematik noch durchgeführt werden muss). Es ist fehleranfällig, schwieriger zu bearbeiten und schwerer zu lesen.
Chris Uzdavinis
11

Warum verursacht die Verwendung eines nicht signierten Int mit größerer Wahrscheinlichkeit Fehler als die Verwendung eines signierten Int?

Die Verwendung eines nicht signierten Typs führt nicht häufiger zu Fehlern als die Verwendung eines signierten Typs mit bestimmten Aufgabenklassen.

Verwenden Sie das richtige Werkzeug für den Job.

Was ist falsch an modularer Arithmetik? Ist das nicht das erwartete Verhalten eines Int ohne Vorzeichen?
Warum verursacht die Verwendung eines nicht signierten Int mit größerer Wahrscheinlichkeit Fehler als die Verwendung eines signierten Int?

Wenn die Aufgabe gut übereinstimmt: nichts falsch. Nein, nicht wahrscheinlicher.

Sicherheits-, Verschlüsselungs- und Authentifizierungsalgorithmen setzen auf vorzeichenlose modulare Mathematik.

Auch Komprimierungs- / Dekomprimierungsalgorithmen sowie verschiedene Grafikformate profitieren und sind mit vorzeichenloser Mathematik weniger fehlerhaft.

Jedes Mal, wenn bitweise Operatoren und Verschiebungen verwendet werden, werden die vorzeichenlosen Operationen nicht mit den Vorzeichenerweiterungsproblemen der vorzeichenbehafteten Mathematik durcheinander gebracht .


Vorzeichenbehaftete Ganzzahlmathematik hat ein intuitives Erscheinungsbild, das von allen, einschließlich der Lernenden für das Codieren, leicht verstanden wird. C / C ++ war ursprünglich nicht als Ziel gedacht und sollte jetzt keine Intro-Sprache sein. Für eine schnelle Codierung, bei der Sicherheitsnetze für den Überlauf verwendet werden, sind andere Sprachen besser geeignet. Für schlanken, schnellen Code geht C davon aus, dass Codierer wissen, was sie tun (sie sind erfahren).

Eine Falle der signierten Mathematik ist heute das allgegenwärtige 32-Bit int, das bei so vielen Problemen für die allgemeinen Aufgaben ohne Bereichsprüfung gut genug ist. Dies führt zu Selbstzufriedenheit, gegen die der Überlauf nicht codiert ist. Stattdessen for (int i=0; i < n; i++) int len = strlen(s);wird als OK angesehen , da nwird angenommen , < INT_MAXund Streicher werden nie zu lang sein, und nicht voll im ersten Fall oder Verwendung geschützt wird reichte size_t, unsignedoder sogar long longin den zweiten.

C / C ++ wurde in einer Zeit entwickelt, die sowohl 16-Bit als auch 32-Bit umfasste, intund das zusätzliche Bit, das ein nicht signiertes 16-Bit size_tbietet, war signifikant. In Bezug auf Überlaufprobleme war Aufmerksamkeit erforderlich, sei es intoder unsigned.

Mit 32-Bit- (oder breiteren) Anwendungen von Google auf Nicht-16-Bit- int/unsignedPlattformen wird aufgrund der intgroßen Reichweite der +/- Überlauf nicht berücksichtigt . Dies macht Sinn , für solche Anwendungen zu fördern , intüber unsigned. Dennoch ist intMathe nicht gut geschützt.

Die engen 16-Bit- int/unsignedBedenken gelten heute für ausgewählte eingebettete Anwendungen.

Die Richtlinien von Google gelten gut für Code, den sie heute schreiben. Dies ist keine endgültige Richtlinie für den größeren Bereich von C / C ++ - Code.


Ein Grund, warum ich mir vorstellen kann, signiertes int anstelle von nicht signiertem int zu verwenden, ist, dass es leichter zu erkennen ist, wenn es überläuft (zu negativ).

In C / C ++ ist ein vorzeichenbehafteter int math-Überlauf ein undefiniertes Verhalten und daher sicherlich nicht einfacher zu erkennen als das definierte Verhalten vorzeichenloser Mathematik.


Wie @Chris Uzdavinis gut kommentierte, wird das Mischen von signierten und nicht signierten Dateien am besten von allen (insbesondere Anfängern) vermieden und bei Bedarf sorgfältig codiert.

chux - Monica wieder einsetzen
quelle
2
Sie machen einen guten Punkt, dass ein intdas Verhalten einer "tatsächlichen" Ganzzahl auch nicht modelliert. Undefiniertes Verhalten beim Überlauf ist nicht die Meinung eines Mathematikers zu ganzen Zahlen: Es gibt keine Möglichkeit eines "Überlaufs" mit einer abstrakten ganzen Zahl. Aber das sind Maschinenspeichereinheiten, keine Zahlen eines Mathematikers.
Tchrist
1
@tchrist: Vorzeichenloses Verhalten beim Überlauf ist die Art und Weise, wie ein Mathematiker über einen abstrakten algebraischen Ring von ganzzahligen kongruenten Mods (type_MAX + 1) denken würde.
Supercat
Wenn Sie gcc verwenden, ist ein signed intÜberlauf leicht zu erkennen (mit -ftrapv), während ein nicht signierter "Überlauf" schwer zu erkennen ist.
Anatolyg
5

Ich habe einige Erfahrungen mit Googles Styleguide, AKA, dem Per Anhalter durch verrückte Programmierer, die vor langer Zeit in das Unternehmen eingestiegen sind. Diese spezielle Richtlinie ist nur ein Beispiel für die Dutzende verrückter Regeln in diesem Buch.

Fehler treten nur bei vorzeichenlosen Typen auf, wenn Sie versuchen, mit ihnen zu rechnen (siehe Beispiel von Chris Uzdavinis oben), dh wenn Sie sie als Zahlen verwenden. Vorzeichenlose Typen sind nicht zum Speichern numerischer Mengen vorgesehen, sondern zum Speichern von Zählwerten wie der Größe von Behältern, die niemals negativ sein können, und sie können und sollten für diesen Zweck verwendet werden.

Die Idee, arithmetische Typen (wie vorzeichenbehaftete Ganzzahlen) zum Speichern von Containergrößen zu verwenden, ist idiotisch. Würden Sie auch ein Double verwenden, um die Größe einer Liste zu speichern? Dass es bei Google Mitarbeiter gibt, die Containergrößen mit arithmetischen Typen speichern und von anderen verlangen, dass sie dasselbe tun, sagt etwas über das Unternehmen aus. Eine Sache, die ich an solchen Diktaten bemerke, ist, dass je dümmer sie sind, desto mehr müssen sie strenge Do-it-or-you-are-entlassen-Regeln sein, da sonst Menschen mit gesundem Menschenverstand die Regel ignorieren würden.

Tyler Durden
quelle
Während ich Ihre Abweichung verstehe, würden die pauschalen Anweisungen bitweise Operationen praktisch eliminieren, wenn unsignedTypen nur Zählungen enthalten könnten und nicht in der Arithmetik verwendet werden könnten. Daher ist der Teil "Wahnsinnige Anweisungen von schlechten Programmierern" sinnvoller.
David C. Rankin
@ DavidC.Rankin Bitte nimm es nicht als "pauschale" Aussage. Offensichtlich gibt es mehrere legitime Verwendungen für vorzeichenlose Ganzzahlen (wie das Speichern von bitweisen Werten).
Tyler Durden
Ja, ja - ich habe es nicht getan, deshalb habe ich gesagt "Ich verstehe deinen Drift."
David C. Rankin
1
Zählungen werden oft mit Dingen verglichen, auf denen arithmetisch gearbeitet wird, wie z. B. Indizes. Die Art und Weise, wie C Vergleiche mit vorzeichenbehafteten und vorzeichenlosen Zahlen durchführt, kann zu vielen seltsamen Macken führen. Außer in Situationen, in denen der Spitzenwert einer Zählung in einen vorzeichenlosen, aber nicht in den entsprechenden vorzeichenbehafteten Typ passt (in den Tagen intmit 16 Bit üblich, heute jedoch weitaus weniger), ist es besser, Zählungen zu haben, die sich wie Zahlen verhalten.
Supercat
1
"Fehler treten nur bei vorzeichenlosen Typen auf, wenn Sie versuchen, mit ihnen zu rechnen" - was die ganze Zeit passiert. "Die Idee, arithmetische Typen (wie vorzeichenbehaftete Ganzzahlen) zum Speichern von Containergrößen zu verwenden, ist idiotisch" - Dies ist nicht der Fall, und das C ++ - Komitee betrachtet es jetzt als historischen Fehler, size_t zu verwenden. Der Grund? Implizite Konvertierungen.
Átila Neves
1

Verwenden von vorzeichenlosen Typen zur Darstellung nicht negativer Werte ...

  • ist wahrscheinlicher , Fehler zu verursachen, die eine Typwerbung beinhalten, wenn signierte und nicht signierte Werte verwendet werden, wie andere Antworten zeigen und ausführlich diskutieren, aber
  • Es ist weniger wahrscheinlich , dass Fehler bei der Auswahl von Typen mit Domänen auftreten, die unerwünschte / nicht zulässige Werte darstellen können. An einigen Stellen wird davon ausgegangen, dass sich der Wert in der Domäne befindet, und es kann zu unerwartetem und potenziell gefährlichem Verhalten kommen, wenn sich ein anderer Wert irgendwie einschleicht.

In den Google Coding Guidelines wird der Schwerpunkt auf die erste Art der Überlegung gelegt. Andere Richtliniensätze, wie die C ++ - Kernrichtlinien , legen mehr Wert auf den zweiten Punkt. Betrachten Sie beispielsweise die Kernrichtlinie I.12 :

I.12: Deklarieren Sie einen Zeiger, der nicht null sein darf, als not_null

Grund

Um zu vermeiden, dass Nullptr-Fehler dereferenziert werden. Verbesserung der Leistung durch Vermeidung redundanter Überprüfungen für nullptr.

Beispiel

int length(const char* p);            // it is not clear whether length(nullptr) is valid
length(nullptr);                      // OK?
int length(not_null<const char*> p);  // better: we can assume that p cannot be nullptr
int length(const char* p);            // we must assume that p can be nullptr

Durch die Angabe der Absicht in der Quelle können Implementierer und Tools eine bessere Diagnose bereitstellen, z. B. das Auffinden einiger Fehlerklassen durch statische Analyse, und Optimierungen durchführen, z. B. das Entfernen von Verzweigungen und Nulltests.

Natürlich könnten Sie für einen non_negativeWrapper für Ganzzahlen argumentieren , der beide Fehlerkategorien vermeidet, aber das hätte seine eigenen Probleme ...

einpoklum
quelle
0

In der Google-Anweisung geht es darum, unsigned als Größentyp für Container zu verwenden . Im Gegensatz dazu scheint die Frage allgemeiner zu sein. Bitte denken Sie daran, während Sie weiterlesen.

Da die meisten Antworten bisher auf die Google-Aussage reagierten, weniger auf die größere Frage, werde ich meine Antwort zu negativen Containergrößen beginnen und anschließend versuchen, jeden (hoffnungslos, ich weiß ...) davon zu überzeugen, dass unsigniert gut ist.

Signierte Behältergrößen

Nehmen wir an, jemand hat einen Fehler codiert, der zu einem negativen Containerindex führt. Das Ergebnis ist entweder undefiniertes Verhalten oder eine Ausnahme- / Zugriffsverletzung. Ist das wirklich besser, als undefiniertes Verhalten oder eine Ausnahme- / Zugriffsverletzung zu erhalten, wenn der Indextyp nicht signiert war? Ich denke nicht.

Nun gibt es eine Klasse von Menschen, die gerne über Mathematik und das, was in diesem Zusammenhang "natürlich" ist, sprechen. Wie kann ein integraler Typ mit negativer Zahl natürlich sein, um etwas zu beschreiben, das von Natur aus> = 0 ist? Verwenden Sie Arrays mit negativen Größen viel? Meiner Meinung nach würden besonders mathematisch veranlagte Personen diese Nichtübereinstimmung der Semantik (Größe / Indextyp sagt, dass negativ möglich ist, während ein Array mit negativer Größe schwer vorstellbar ist) als irritierend empfinden.

Die einzige Frage, die in dieser Angelegenheit noch offen ist, ist, ob ein Compiler - wie im Google-Kommentar angegeben - tatsächlich aktiv bei der Suche nach solchen Fehlern helfen kann. Und noch besser als die Alternative, bei der es sich um unterlaufgeschützte Ganzzahlen ohne Vorzeichen handelt (x86-64-Assembly und wahrscheinlich andere Architekturen haben Mittel, um dies zu erreichen, nur C / C ++ verwendet diese Mittel nicht). Die einzige Möglichkeit, die ich mir vorstellen kann, besteht darin, dass der Compiler automatisch Laufzeitprüfungen ( if (index < 0) throwOrWhatever) hinzufügt oder im Falle von Aktionen zur Kompilierungszeit viele potenziell falsch positive Warnungen / Fehler erzeugt. "Der Index für diesen Array-Zugriff könnte negativ sein." Ich habe meine Zweifel, das wäre hilfreich.

Außerdem ist es für Leute, die tatsächlich Laufzeitprüfungen für ihre Array- / Containerindizes schreiben, mehr Arbeit, sich mit vorzeichenbehafteten Ganzzahlen zu befassen. Anstatt zu schreiben, müssen if (index < container.size()) { ... }Sie jetzt schreiben : if (index >= 0 && index < container.size()) { ... }. Sieht für mich nach Zwangsarbeit aus und nicht nach einer Verbesserung ...

Sprachen ohne vorzeichenlose Typen saugen ...

Ja, das ist ein Stich in Java. Jetzt komme ich aus dem Hintergrund der eingebetteten Programmierung und wir haben viel mit Feldbussen gearbeitet, bei denen binäre Operationen (und oder oder xor, ...) und die bitweise Zusammensetzung von Werten buchstäblich das A und O sind. Für eines unserer Produkte wollten wir - oder besser gesagt ein Kunde - einen Java-Port ... und ich saß dem glücklicherweise sehr kompetenten Mann gegenüber, der den Port gemacht hat (ich lehnte ab ...). Er versuchte, gelassen zu bleiben ... und in der Stille zu leiden ... aber der Schmerz war da, er konnte nicht aufhören zu fluchen, nachdem er sich einige Tage lang ständig mit vorzeichenbehafteten Integralwerten befasst hatte, die nicht vorzeichenlos sein SOLLTEN ... Selbst wenn er Unit-Tests für schrieb Diese Szenarien sind schmerzhaft und ich persönlich denke, Java wäre besser dran gewesen, wenn sie vorzeichenbehaftete Ganzzahlen weggelassen und nur vorzeichenlose angeboten hätten ... zumindest dann müssen Sie sich nicht um Zeichenerweiterungen usw. kümmern.

Das sind meine 5 Cent in dieser Angelegenheit.

BitTickler
quelle