Warum Ganzzahlen überraschen
Eine Maschinen-Ganzzahl hat eine feste Anzahl Bits, jede Operation kann also einen Wert liefern, der nicht hineinpasst. Was dann passiert, entscheidet nicht die Hardware (die einfach umläuft), sondern die Sprache:
| Sprache | Überlauf mit Vorzeichen | Überlauf ohne Vorzeichen | Shift um ≥ Breite | Division durch null |
|---|---|---|---|---|
| C / C++ | undefiniertes Verhalten: Der Optimierer nimmt an, dass es nie passiert | läuft modulo 2ⁿ um (definiert) | undefiniert | undefiniert (SIGFPE auf x86) |
| Rust | Panic im Debug-Modus, Wrap im Release; checked_/wrapping_/saturating_/overflowing_ sind explizit | wie mit Vorzeichen | Panic im Debug-Modus | Panic |
| Go | läuft um (definiert) | läuft um | 0 / −1, kein Panic | Panic |
| Java | läuft um (definiert); Math.addExact wirft | keine vorzeichenlosen Typen (char ist 16-Bit unsigned) | Zähler auf 5/6 Bit maskiert | ArithmeticException |
| JavaScript | Zahlen sind Doubles: exakt bis 2⁵³, danach Rundung; Bit-Operatoren arbeiten auf int32 | Zähler auf 5 Bit maskiert | Infinity / NaN | |
Die C-Regeln, die zubeißen
- Integer-Promotion. Alles, was kleiner als
intist, wird vor der Arithmetik zuint, also istuint8_t a = 200, b = 100; a + bgleich 300, und erst die Zuweisung zurück in einuint8_tschneidet auf 44 ab.~xauf einemuint8_tergibt einen negativen int. - Übliche arithmetische Konvertierungen. Trifft signed auf unsigned gleicher Breite, wird die signed-Seite unsigned:
-1 > 0uist wahr,if (len - 1 < 0)ist nie wahr, wennleneinsize_tist. - Signed-Überlauf ist UB.
if (x + 1 < x)als Überlaufprüfung wird von GCC und Clang mit Optimierung gelöscht, weil sie annehmen dürfen, dassx + 1nie überläuft. Prüfen Sie vor der Operation (x > INT_MAX - y) oder verwenden Sie__builtin_add_overflow. - Shifts. Um die Breite oder mehr zu schieben oder einen negativen Wert links zu schieben ist undefiniert; x86 maskiert den Zähler, ARM liefert null, der Compiler tut, was sein Modell sagt.
- Division. Schneidet Richtung null ab, also
-7 / 2 == -3und-7 % 2 == -1;INT_MIN / -1läuft über und löst auf x86 tatsächlich einen Trap aus.
Die Ausgabe lesen
Das Ergebnis wird in dem Typ gezeigt, in dem es landet, mit seinem Bitmuster und denselben Bits in der entgegengesetzten Vorzeichenlesart, also dem, was Sie sehen, wenn ein Wert eine (int)-/(unsigned)-Grenze überschreitet oder durch einen anderen Typ gespeichert und zurückgelesen wird. Die Schrittliste ist die Auswertungsreihenfolge mit dem Typ in jedem Schritt; die Hinweise sind die Stellen, an denen die Sprache etwas anderes als einfache Arithmetik getan hat.
Fragen
Warum weicht das C-Ergebnis von dem ab, was mein Programm ausgegeben hat?
Für undefiniertes Verhalten gibt es keine einzige richtige Antwort: Diese Seite zeigt den Wraparound-Wert, den unoptimierter Code liefert, und markiert den Schritt als UB. Mit -O2 hat der Compiler vielleicht angenommen, dass der Überlauf nicht vorkommen kann, und anderen Code erzeugt. Kompilieren Sie mit -fsanitize=undefined, um die genaue Stelle zu sehen.
Was macht die Typauswahl?
Sie bestimmt den Typ von Literalen ohne Suffix oder Cast, der in C standardmäßig int ist. Wählen Sie u8 und tippen Sie 200 + 100, um Rust panicken zu sehen, oder C auf int promoten und gelingen.
Wie schreibe ich einen Cast?
C-Stil: (u8) expr, (uint32_t) expr, (int) expr. Rust-Stil: expr as u8. Beides funktioniert in jedem Sprachmodus. INT_MAX, INT_MIN, UINT_MAX, LONG_MAX, SIZE_MAX stehen als Namen zur Verfügung.
Kann es 128 Bit?
Ja: i128 und u128 werden exakt ausgewertet (alle Arithmetik läuft mit beliebiger Genauigkeit und wird dann in den Typ eingepasst).
Und Gleitkommazahlen?
Nicht hier; dafür gibt es den IEEE-754-Float-Visualizer auf dieser Seite. Dieses Tool behandelt nur, was mit ganzen Zahlen in Typen fester Breite passiert.