Über Überläufer und andere Verbrecher

Zu den Fehlern, die sich am besten verstecken können, gehören Überläufe. So grenzt es eigentlich an ein Wunder, dass nicht mehr Programmiersprachen unterstützend gegen diese Bösewichte vorgehen.

In Pocket speichern vorlesen Druckansicht 99 Kommentare lesen
Lesezeit: 5 Min.
Von
  • Michael Wiedeking

Zu den Fehlern, die sich am besten verstecken können, gehören Überläufe. So grenzt es eigentlich an ein Wunder, dass nicht mehr Programmiersprachen unterstützend gegen diese Bösewichte vorgehen.

Zumindest für iOS-Entwickler ist Swift zu einer unverzichtbaren Alternative zu Objective-C geworden. Swift hat eigentlich alles zu bieten, was man von einer modernen Programmiersprache verlangen kann. Sie zeichnet sich aber durch eine Eigenschaft aus, die sie von den meisten anderen unterscheidet.

Mit Einführung der 64-Bit-Rechner scheint es ja fast keine praktischen Grenzen mehr zu geben. Die größte vorzeichenlose ganze Zahl ist dann 18.446.744.073.709.551.615, die größte vorzeichenbehaftete Zahl 9.223.372.036.854.775.807; Zahlen mit beeindruckenden 20 beziehunhsweise 19 Stellen – also in der Größenordnung mehrerer Trillionen. Dennoch empfehlen viele Prozessorhersteller, wenn es sich denn nicht um eine reine 64-Bit-Architektur handelt, immer noch mit 32-Bit-Zahlen zu arbeiten, denn der Flaschenhals der Prozessorperformanz ist der Zugriff auf den Speicher. Dann liegen die Grenzen immerhin noch bei 4.294.967.295 beziehungsweise 2.147.483.647, also zehnstellig, immer noch im unteren Milliardenbereich.

Das dies aber doch oft nicht genug ist, zeigen auch die ersten Videos bei Youtube, die mehr als zwei Milliarden Mal angeschaut wurden. Das erfolgreichste Video wurde inzwischen fast 2,8 Milliarden mal angesehen, womit eine vorzeichenbehaftete 32-Bit-Zahl zur Speicherung des Zählers nicht mehr in Frage kommt. Aber auch in anderen Bereichen reichen 32 Bit gelegentlich nicht mehr aus, weil etwa mit Big Data auch die Datenmengen zunehmend mächtiger werden.

Damit stellt sich allerdings das Problem, wann es denn tatsächlich nicht mehr reicht. Das herauszufinden ist oft gar nicht so leicht. Die Annahmen, die man macht, erweisen sich nämlich allzu oft als nicht ausreichend. An dieser Stelle fallen mir zwei berühmte Beispiele ein: Der Weltmarkt für Computer liegt bei fünf Stück und 640 Kilobyte Speicher ist für jedermann mehr als genug. Letzteres hatte übrigens einfach architekturbedingte Gründe und hat keiner so richtig gesagt; aber es ist so eine nette Geschichte. Losgelöst davon unterschätzt man vielleicht auch einfach, was die ganzen Zahlen im Rechner zu leisten vermögen.

Betrachtet man die einfachen arithmetischen Operationen, kann man sehen, dass sie überraschend oft völlig falsche Ergebnisse liefern. Die Addition vorzeichenloser Zahlen beispielsweise läuft in knapp 50 Prozent der Fälle über.

Summe x + y der vorzeichenlosen Werte x und y (schwarz) und deren Überläufe (rot)

Bei der Addition vorzeichenbehafteter Zahlen sieht es schon ein bisschen besser aus, aber auch diese versagt noch bei etwa 25 Prozent der möglichen Kombinationen.

Summe x + y der vorzeichenbehafteten Werte x und y (schwarz) und deren Überläufe (rot)

Bei der vorzeichenlosen und vorzeichenbehafteten Subtraktion ist es analog, nur dass hierbei die nicht definierten Bereiche woanders liegen (gespiegelt an der Diagonalen bzw. der y-Achse).

Leider wird es bei der Multiplikation nicht besser. Ganz im Gegenteil, es wird katastrophal. Ungefähr 75 Prozent (sic!) der möglichen Multiplikation schlagen wegen Überlaufs fehl.

Produkt x · y der vorzeichenlosen Werte x und y (schwarz) und dessen Überläufe (rot)

Wie auch bei der Addition ist die Multiplikation vorzeichenbehafteter Zahlen nicht ganz so kritisch: Hier schlagen "nur" 50 Prozent fehl.

Produkt x · y der vorzeichenbehafteten Werte x und y (schwarz) und dessen Überläufe (rot)

Der Vollständigkeit halber sei hier noch die Division erwähnt. Im vorzeichenlosen Fall schlägt diese nur bei der Division durch Null fehl, weil dies (bei der ganzzahligen) Division ja nicht definiert ist. Das ist bei der vorzeichenbehafteten Division nicht anders; allerdings gibt es hier doch einen einzigen Fall, in dem diese überläuft: Teilt man (das Zweierkomplement bei den Zahlen vorausgesetzt) die kleinste negative Zahl durch
–1, lässt sich das Ergebnis leider nicht in einer vorzeichenlosen Zahl darstellen und man erhält wegen dieses Überlaufs wieder die kleinste negative Zahl.

Ganzzahliger Quotient x / y der vorzeichenbehafteten Werte x und y (schwarz), dessen undefinierte Kombinationen (rot, gefüllt) und einziger Überlauf (rot, offen)

Angesichts dieser erschreckenden Umstände ist es eigentlich mehr als verwunderlich, dass man vor solchen Überläufen nicht gewarnt wird. Dabei bekäme man das bei vielen der modernen Prozessoren problemlos geschenkt. Aber irgendwie gehen die meisten Programmiersprachen davon aus, dass die Entwickler das im Griff haben.

Ausgenommen Swift. Hier sind alle arithmetischen Grundfunktion – Addition, Subtraktion, Multiplikation und Division – vor (unerkannten) Überläufen geschützt. Sobald ein Überlauf auftritt, wird das als Fehler signalisiert. Das scheint zunächst ein bisschen rigoros zu sein, aber bei der Division durch Null hat man sich ja auch schon dran gewöhnt.

In dem Fall, dass ein Überlauf erwünscht ist, ist dies durch besondere Operatoren machbar. Stellt man nämlich den Operator-Symbolen ein Kaufmanns-und voran, so werden die Operationen Modulo 2n durchgeführt, wobei n die Registerbreite ist. So liefert

x &+ y

immer ein (möglicherweise übergelaufenes) Ergebnis, in der Hoffnung, dass es in dem gegebenen Kontext korrekt ist. In manchen Fällen kann das ja auch tatsächlich sinnvoll sein, wenn etwa Zwischenergebnisse überlaufen dürfen, weil das Endergebnis trotzdem korrekt ist.

Mir gefällt Swifts Einstellung zu dieser potenziellen Fehlerquelle sehr gut. Bei anderen Sprachen (etwa bei C#) muss dieses Verhalten explizit eingeschaltet werden. Aber wer macht das schon – vor allem, wenn nicht klar ist, wie groß die Gefahr eigentlich ist.

Die Kehrseite der Medaille ist hier aber der Umstand, dass sich die Entwickler von Swift dazu entschieden haben, bei Fehlern dieser Art die Applikation abzubrechen; ein Abfangen von Fehlern dieser Art ist leider nicht möglich. Schade eigentlich. ()