Ist der vorzeichenbehaftete Ganzzahlüberlauf in C ++ immer noch undefiniertes Verhalten?

83

Wie wir wissen, ist ein vorzeichenbehafteter Ganzzahlüberlauf ein undefiniertes Verhalten . In der C ++ 11- cstdintDokumentation gibt es jedoch etwas Interessantes :

Ganzzahliger Typ mit Vorzeichen mit einer Breite von genau 8, 16, 32 bzw. 64 Bit ohne Auffüllbits und Verwendung des Zweierkomplements für negative Werte (nur bereitgestellt, wenn die Implementierung den Typ direkt unterstützt)

Siehe Link

Und hier ist meine Frage: Da die Norm ausdrücklich sagt , dass für int8_t, int16_t, int32_tund int64_tnegative Zahlen sind 2-Komplement, ist immer noch Überlauf dieser Art ein nicht definiertes Verhalten?

Bearbeiten Ich habe die C ++ 11- und C11-Standards überprüft und Folgendes gefunden:

C ++ 11, §18.4.1:

Der Header definiert alle Funktionen, Typen und Makros wie 7.20 im C-Standard.

C11, §7.20.1.1:

Der Typedef-Name intN_tbezeichnet einen vorzeichenbehafteten Integer-Typ mit der Breite N, keinen Füllbits und einer Zweierkomplementdarstellung. Somit int8_tbezeichnet ein solcher vorzeichenbehaftete Ganzzahl - Typ mit einer Breite von genau 8 Bits.

Archie
quelle
14
Vergessen Sie niemals, dass die einzige primäre Dokumentation für C ++ der Standard ist. Alles andere, sogar ein Wiki wie CppReference, ist eine sekundäre Quelle. Das heißt nicht, dass es falsch ist; nur nicht ganz zuverlässig.
Nicol Bolas
Ich würde erwarten, dass es UB ist, es gibt keine Ausnahme für diese Typen in C, ich verstehe nicht, warum C ++ eine hinzufügen würde.
Daniel Fischer
3
Ich bin ein bisschen verwirrt: Wo ist das Verb im Satz "vorzeichenbehafteter Integer-Typ mit einer Breite von genau 8, 16, 32 bzw. 64 Bit ohne Auffüllbits und unter Verwendung des Zweierkomplements für negative Werte (nur bereitgestellt, wenn die Implementierung dies direkt unterstützt) der Typ)?" Fehlt etwas? Was bedeutet das?
YSC
C ++ 11 basiert auf C99, nicht auf C11. Aber das ist sowieso nicht wichtig
LF

Antworten:

80

Ist ein Überlauf dieser Art immer noch ein undefiniertes Verhalten?

Ja. Gemäß Absatz 5/4 des C ++ 11-Standards (in Bezug auf alle Ausdrücke im Allgemeinen):

Wenn während der Auswertung eines Ausdrucks das Ergebnis nicht mathematisch definiert ist oder nicht im Bereich der darstellbaren Werte für seinen Typ liegt, ist das Verhalten undefiniert . [...]

Die Tatsache, dass für diese vorzeichenbehafteten Typen eine Zweierkomplementdarstellung verwendet wird, bedeutet nicht, dass bei der Bewertung von Ausdrücken dieser Typen das arithmetische Modulo 2 ^ n verwendet wird.

In Bezug auf vorzeichenlose Arithmetik legt der Standard andererseits ausdrücklich fest, dass (Ziffer 3.9.1 / 4):

Ganzzahlen ohne Vorzeichen, deklariert unsigned , sind die Gesetze der Arithmetik modulo gehorchen 2 ^ n , wobei n die Anzahl von Bits in der Wertdarstellung dieser bestimmten Größe der ganzzahligen

Dies bedeutet, dass das Ergebnis einer vorzeichenlosen arithmetischen Operation immer " mathematisch definiert " ist und das Ergebnis immer innerhalb des darstellbaren Bereichs liegt; daher gilt 5/4 nicht. Fußnote 46 erklärt dies:

46) Dies impliziert, dass die vorzeichenlose Arithmetik nicht überläuft, da ein Ergebnis, das nicht durch den resultierenden vorzeichenlosen Ganzzahltyp dargestellt werden kann, modulo um die Zahl reduziert wird, die eins größer ist als der größte Wert, der durch den resultierenden vorzeichenlosen Ganzzahltyp dargestellt werden kann.

Andy Prowl
quelle
1
Dieser Absatz würde auch bedeuten, dass ein nicht signierter Überlauf undefiniert ist, was nicht der Fall ist.
Archie
8
@Archie: Nicht wirklich, da vorzeichenlose Werte modulo für den vorzeichenlosen Bereich definiert sind .
Leichtigkeitsrennen im Orbit
3
@Archie: Ich habe versucht zu klären, aber im Grunde haben Sie die Antwort von LightnessRacesinOrbit
Andy Prowl
1
Es spielt eigentlich keine Rolle, ob ein vorzeichenloser Überlauf definiert ist oder nicht, wenn er aufgrund einer Modulo-Berechnung nicht auftreten kann ...
Aconcagua
1
Es gibt vorzeichenlose Operationen, deren Ergebnis nicht "mathematisch definiert" ist - insbesondere die Division durch Null -, sodass Ihre Formulierung möglicherweise nicht ganz dem entspricht, was Sie in diesem Satz gemeint haben. ITYM: Wenn das Ergebnis mathematisch definiert ist , wird es auch in C ++ definiert.
Toby Speight
22

Nur weil ein Typ für die Verwendung der 2s-Komplementdarstellung definiert ist, folgt daraus nicht, dass der arithmetische Überlauf in diesem Typ definiert wird.

Das undefinierte Verhalten des vorzeichenbehafteten arithmetischen Überlaufs wird verwendet, um Optimierungen zu ermöglichen. Zum Beispiel kann der Compiler annehmen, dass wenn a > bdann a + 1 > bauch; Dies gilt nicht für vorzeichenlose Arithmetik, bei der die zweite Prüfung durchgeführt werden müsste, da die Möglichkeit besteht, dass a + 1sich dies ändert 0. Einige Plattformen können auch ein Trap-Signal bei arithmetischem Überlauf erzeugen (siehe z. B. http://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html ). Der Standard lässt dies weiterhin zu.

ecatmur
quelle
5
Es mag erwähnenswert sein, dass sich viele Menschen mehr um die Möglichkeiten von Traps "sorgen", aber die Compiler-Annahmen sind tatsächlich heimtückischer (einer der Gründe, warum ich mir eine Kategorie zwischen implementierungsdefiniertem und undefiniertem Verhalten wünschte - im Gegensatz zu implementierungsdefiniertem Verhalten Ich möchte ein "implementierungsbeschränktes" Verhalten, bei dem Implementierungen alles angeben müssen, was als Folge von etwas passieren kann (die Spezifikationen könnten explizit undefiniertes Verhalten enthalten, aber ...). .
Supercat
3
... Implementierungen würden ermutigt, wenn sie praktikabler sind). Auf einer Hardware, bei der Zweierkomplementzahlen auf natürliche Weise "umbrochen" werden, gibt es keinen vernünftigen Grund für Code, der möchte, dass ein Ergebnis mit umschlossenen Ganzzahlen viele Befehle ausführt, die versuchen, ohne Berechnung einer Ganzzahl eine Berechnung durchzuführen, die die Hardware in nur einem oder zwei Befehlen ausführen könnte .
Supercat
1
@supercat Tatsächlich kann der Code, der ein umschlossenes Ergebnis haben möchte (auf 2-Komplement-CPUs) einfach Operanden in entsprechende vorzeichenlose Typen umwandeln und die Operation ausführen (und dann zurücksetzen, um einen implementierungsdefinierten Wert zu erhalten): Dies funktioniert für Addition, Subtraktion und Multiplikation . Das einzige Problem ist mit Division, Modulo und solchen Funktionen wie abs. Für diese Operationen, wenn es funktioniert, sind nicht mehr Anweisungen erforderlich als mit signierter Arthmetik.
Ruslan
@ Ruslan: In Fällen, in denen Code ein genau verpacktes Ergebnis benötigt, wäre eine Umwandlung in unsigniert hässlich, würde aber nicht unbedingt zusätzlichen Code generieren. Ein größeres Problem wäre der Code, der schnell "potenziell interessante" Kandidaten identifizieren muss, die die meiste Zeit damit verbringen, uninteressante Kandidaten abzulehnen. Wenn man einem Compiler die Freiheit gibt, willkürlich zusätzliche Genauigkeit mit vorzeichenbehafteten ganzzahligen Werten beizubehalten oder zu verwerfen, aber erfordert, dass eine Umwandlung in einen ganzzahligen Typ eine solche Genauigkeit abschneidet, ermöglicht dies die meisten nützlichen Optimierungen, die durch Überlauf-UB erzielt würden , ...
Supercat
... würde aber zulassen, dass Code, der präzise (int)(x+y)>zumbrochen werden muss, eine Umwandlung anstelle von zwei verwendet (z. B. würde ein umbrochenes Ergebnis verglichen), und würde es Programmierern auch ermöglichen, x+y>zin Fällen zu schreiben, in denen es akzeptabel wäre, wenn Code für den Fall 0 oder 1 ergibt Überlauf vorausgesetzt, es hat keine andere Nebenwirkung . Wenn entweder 0 oder 1 ein ebenso akzeptables Ergebnis wäre, würde der Programmierer dies schreiben lassen (long)x+y>zoder (int)((unsigned)x+y)>zes den Compilern ermöglichen, auszuwählen, welche der letzteren Funktionen in einem bestimmten Kontext billiger war [jede wäre in einigen Fällen billiger].
Supercat
1

Ich würde es wetten.

Aus der Standarddokumentation (S. 4 und 5):

1.3.24 undefiniertes Verhalten

Verhalten, für das diese Internationale Norm keine Anforderungen stellt

[Hinweis: Undefiniertes Verhalten kann erwartet werden, wenn diese Internationale Norm keine explizite Definition des Verhaltens enthält oder wenn ein Programm ein fehlerhaftes Konstrukt oder fehlerhafte Daten verwendet. Das zulässige undefinierte Verhalten reicht vom vollständigen Ignorieren der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer für die Umgebung charakteristischen dokumentierten Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit der Ausgabe) einer Diagnosemeldung). Viele fehlerhafte Programmkonstrukte erzeugen kein undefiniertes Verhalten. Sie müssen diagnostiziert werden. - Endnote]

EnzoR
quelle