39

In mehreren modernen Programmiersprachen (einschließlich C++, Java und C#) ermöglicht die Sprache integer overflow zur Laufzeit auftreten, ohne irgendeine Art von Fehlerzustand zu erhöhen.Warum verursachen Sprachen nicht standardmäßig Fehler beim Integerüberlauf?

Betrachten Sie zum Beispiel diese (erfundene) C# -Methode, die die Möglichkeit eines Überlaufs/Unterlaufs nicht berücksichtigt. (Der Kürze halber wird das Verfahren auch nicht den Fall behandeln, in denen die angegebene Liste NULL-Verweis ist.)

//Returns the sum of the values in the specified list. 
private static int sumList(List<int> list) 
{ 
    int sum = 0; 
    foreach (int listItem in list) 
    { 
     sum += listItem; 
    } 
    return sum; 
} 

Wenn diese Methode aufgerufen wird, wie folgt:

List<int> list = new List<int>(); 
list.Add(2000000000); 
list.Add(2000000000); 
int sum = sumList(list); 

Ein Überlauf wird treten in der sumList()-Methode auf (da der int-Typ in C# eine 32-Bit-Ganzzahl mit Vorzeichen ist und die Summe der Werte in der Liste den Wert der maximalen 32-Bit-Ganzzahl mit Vorzeichen überschreitet). Die Summenvariable hat einen Wert von -294967296 (kein Wert von 4000000000); Das ist höchstwahrscheinlich nicht der (hypothetische) Entwickler der sumList-Methode.

Offensichtlich gibt es verschiedene Techniken, die von Entwicklern verwendet werden können, die Möglichkeit einer Integer-Überlauf, wie unter Verwendung eines Typ wie Java BigInteger oder dem checked Schlüsselwort und /checked Compiler-Schalter in C# zu vermeiden. Die Frage, die mich interessiert, ist die Frage, warum diese Sprachen so entworfen wurden, dass standardmäßig Integer-Überläufe zuerst vorkommen, anstatt zum Beispiel eine Ausnahme auszulösen, wenn zur Laufzeit eine Operation ausgeführt wird würde zu einem Überlauf führen. Es scheint, als würde ein solches Verhalten dazu beitragen, Fehler zu vermeiden, wenn ein Entwickler es versäumt, die Möglichkeit eines Überlaufs zu berücksichtigen, wenn er Code schreibt, der eine arithmetische Operation ausführt, die zu einem Überlauf führen könnte. (Diese Sprachen könnten etwas wie ein "ungeprüftes" Schlüsselwort enthalten, das einen Block angeben könnte, in dem Integerüberlauf zulässig ist, ohne dass eine Ausnahme ausgelöst wird. In den Fällen, in denen das Verhalten explizit vom Entwickler beabsichtigt ist. C# eigentlich does have this.)

Geht die Antwort einfach auf die Leistung zurück - die Sprachentwickler wollten nicht, dass ihre jeweiligen Sprachen auf "langsame" arithmetische Integer-Operationen zurückgreifen, bei denen die Laufzeit zusätzliche Arbeit leisten müsste, um zu überprüfen, ob ein Überlauf aufgetreten ist jede anwendbare arithmetische Operation - und diese Leistungsüberlegung überwog den Wert der Vermeidung von "stillen" Fehlern in dem Fall, dass ein unbeabsichtigter Überlauf auftritt?

Gibt es andere Gründe für diese Sprachentwurfsentscheidung, abgesehen von Leistungsüberlegungen?

Antwort

31

In C#, war es eine Frage der Leistung. Speziell Out-of-Box-Benchmarking.

Als C# neu war, hoffte Microsoft, dass viele C++ - Entwickler dazu wechseln würden. Sie wussten, dass viele C++ - Leute C++ als schnell empfanden, besonders schneller als Sprachen, die Zeit mit automatischer Speicherverwaltung und dergleichen verschwendet haben.

Sowohl potentielle Adoptierer als auch Magazinkritiker erhalten wahrscheinlich eine Kopie des neuen C#, installieren es, bauen eine triviale App auf, die niemand jemals in der realen Welt schreiben würde, führen es in eine enge Schleife und messen wie lange es dauerte. Dann würden sie eine Entscheidung für ihr Unternehmen treffen oder einen Artikel basierend auf diesem Ergebnis veröffentlichen.

Die Tatsache, dass ihr Test zeigte, dass C# langsamer als nativ kompiliertes C++ ist, ist eine Sache, die Leute schnell von C# abbringen würde.Die Tatsache, dass Ihre C# -App automatisch einen Überlauf/Unterlauf erkennt, ist eine Sache, die sie übersehen könnten. Also, es ist standardmäßig deaktiviert.

Ich denke, es ist offensichtlich, dass 99% der Zeit, die wir wollen/überprüft werden, eingeschaltet sind. Es ist ein unglücklicher Kompromiss.

+2

Wie sehr hätte die Integer-Überlaufprüfung irgendwelche realistischen Benchmarks beeinflusst? Es scheint mir, dass es viele andere Dinge in C# gibt, die viel schlechter sind (zB ein Feld 'readonly rect foo;', eine Aussage wie 'DoSomething (foo.X, foo.Y');' erfordert eine Kopie von ' foo', indem sie ihre 'X'-Accessor-Methode aufruft, eine weitere Kopie von' foo' erstellt und ihre 'Y'-Accessor-Methode aufruft. Ziemlich massiver Overhead - genug, um Integer-Overflow-Checking vergleichsweise trivial erscheinen zu lassen. – supercat

+0

@supercat: In den meisten realen Code wäre "/ checked" standardmäßig nicht messbar langsamer und würde eine bestimmte Klasse von wichtigen Fehlern fangen. Wie ich jedoch in meiner Antwort sagte, eines der ersten Dinge, die ein früher Rezensent von C# könnte Testen Sie die Leistung der Integer-Arithmetik in einer engen Schleife –

+7

Denken Sie daran, dass C# ist jetzt 12 Jahre alt. Damals glaubten viele Programmierer "C++ ist schnell, Java ist langsam, C# ist wie Java". Heute denke ich, die meisten Programmierer sehen die Zeit bis zur Markteinführung und die Bewältigung der Komplexität als einen größeren Einfluss auf das Geschäftserfolg ss diese Tight-Loop-Optimierung. –

24

Ich denke, Leistung ist ein ziemlich guter Grund. Wenn Sie jede Anweisung in einem typischen Programm betrachten, die eine Ganzzahl erhöht, und wenn anstelle der einfachen Op 1 zu addieren, jedes Mal überprüft werden müsste, ob das Hinzufügen von 1 den Typ überschreitet, dann wären die Kosten in zusätzlichen Zyklen ziemlich streng.

+2

Gibt Ihnen die CPU keine Flag zurück, wenn der Vorgang übergelaufen ist? (Das Überprüfen dieser Flagge braucht natürlich auch Zeit). – Thilo

+0

@Thilo gibt es CPUs ohne Flags wie MIPS. Selbst einfache einfache int-Operationen an ihnen sind ein kleiner Schmerz. –

7

Es ist wahrscheinlich 99% Leistung. Auf x86 müsste das Überlauf-Flag bei jeder Operation überprüft werden, was einen enormen Leistungseinbruch bedeuten würde.

Die anderen 1% würden jene Fälle abdecken, in denen Leute Fancy-Bit-Manipulationen vornehmen oder beim Mischen von signierten und unsignierten Operationen "ungenau" sind und die Überlauf-Semantik wollen.

7

Da die Überprüfung auf Überlauf Zeit braucht.Jede primitive mathematische Operation, die normalerweise in eine einzelne Assembleranweisung übersetzt wird, müsste eine Überprüfung auf einen Überlauf enthalten, was zu mehreren Assemblerbefehlen führen würde, was möglicherweise zu einem Programm führen würde, das mehrere Male langsamer ist.

-4

Mein Verständnis, warum Fehler nicht zur Laufzeit standardmäßig ausgelöst werden, läuft auf das Vermächtnis des Erstellens von Programmiersprachen mit ACID-ähnlichem Verhalten hinaus. Genauer gesagt, der Grundsatz, dass alles, was Sie programmieren (oder nicht programmieren), es tun wird (oder nicht tun). Wenn Sie einen Fehler-Handler nicht programmiert haben, dann wird die Maschine aufgrund von keinem Fehler-Handler "annehmen", dass Sie wirklich das lächerliche, absturzanfällige Ding tun wollen, das Sie ihm vorschreiben.

(ACID Referenz: http://en.wikipedia.org/wiki/ACID)

+5

Ich sehe keine Verbindung zwischen den ACID-Eigenschaften und dem, was Sie beschreiben. Bitte erläutern Sie die Verbindung ... –

5

Abwärtskompatibilität ist ein großer. Bei C wurde davon ausgegangen, dass Sie der Größe Ihrer Datentypen genügend Beachtung geschenkt haben, dass bei einem Über-/Unterlauf das gewünscht war. Dann änderte sich mit C++, C# und Java kaum noch, wie die "eingebauten" Datentypen funktionierten.

14

Sie arbeiten unter der Annahme, dass Ganzzahlüberlauf immer unerwünschtes Verhalten ist.

Manchmal Integer-Überlauf ist das gewünschte Verhalten. Ein Beispiel, das ich gesehen habe, ist die Darstellung eines absoluten Kurswertes als Fixpunktnummer. Bei einem unsigned int steht 0 für 0 oder 360 Grad und die maximal 32-Bit-Ganzzahl ohne Vorzeichen (0xffffffff) ist der größte Wert knapp unter 360 Grad.

int main() 
{ 
    uint32_t shipsHeadingInDegrees= 0; 

    // Rotate by a bunch of degrees 
    shipsHeadingInDegrees += 0x80000000; // 180 degrees 
    shipsHeadingInDegrees += 0x80000000; // another 180 degrees, overflows 
    shipsHeadingInDegrees += 0x80000000; // another 180 degrees 

    // Ships heading now will be 180 degrees 
    cout << "Ships Heading Is" << (double(shipsHeadingInDegrees)/double(0xffffffff)) * 360.0 << std::endl; 

} 

Es gibt wahrscheinlich andere Situationen, in denen Überlauf akzeptabel ist, ähnlich wie in diesem Beispiel.

+7

Es gibt wahrscheinlich noch viele weitere Fälle, bei denen der Ganzzahlüberlauf zu falschen Ergebnissen führt, als wenn das richtige Ergebnis angezeigt wird. In der Tat ist das einzige Mal, dass es tatsächlich korrekte Ergebnisse generiert, wenn es das erwartete Verhalten ist und es tatsächlich als Feature verwendet wird, wie in Ihrem Beispiel. – Kibbee

+1

@Kibbee Ich wäre bereit zu wagen, dass in dem Fall, in dem Integer-Überlauf zu einem Fehler führen kann, es mehr ein Indikator dafür ist, dass Sie keine richtige Bereichsüberprüfung als ein Fehler der Programmiersprache getan haben. In den Fällen, in denen dies nicht erwartet wird, sollte Ihr Code darauf achten. – OwenP

+2

Ganz zu schweigen von der inhärenten Gefahr, sich auf ein plattformspezifisches Low-Level-Detail wie dieses zu verlassen. Was passiert, wenn dies auf einer 64-Bit-Plattform neu kompiliert wird? Eek. – Dan

7

C/C++ darf das Trap-Verhalten niemals festlegen. Sogar die offensichtliche Division durch 0 ist ein undefiniertes Verhalten in C++, keine spezifizierte Art von Trap.

Die C-Sprache hat kein Trapping-Konzept, es sei denn, Sie zählen Signale.

C++ hat ein Design-Prinzip, dass es keinen Overhead einführt, der in C nicht vorhanden ist, es sei denn, Sie fragen danach. Strotrup hätte also nicht vorgeben wollen, dass sich ganze Zahlen in einer Weise verhalten, die eine explizite Überprüfung erfordert.

Einige frühe Compiler und Lightweight-Implementierungen für eingeschränkte Hardware unterstützen keine Ausnahmen, und Ausnahmen können oft mit Compiler-Optionen deaktiviert werden. Es wäre problematisch, Ausnahmen für integrierte Sprachen zu erlassen.

Auch wenn C++ gemacht hatte ganze Zahlen geprüft, 99% der Programmierer in den frühen Tagen worden wären, wenn off für die Leistungssteigerung ...

Verwandte Themen