Folgendes gibt mir keinerlei Warnung, wenn es mit gcc 4.5.2 auf einem x86-Rechner mit Linux kompiliert wird:
char foo = 255;
Aber wenn ich benutze -pedantic
gcc sagt:
Warnung: Überlauf bei impliziter Konstantenkonvertierung
Die Art und Weise, wie sich gcc verhält, ist etwas seltsam und lässt mich zweifeln, ob ich wirklich verstehe, was in dieser Aufgabe vor sich geht. Ich denke, wenn char
auf POSIX 8 Bit lang ist und standardmäßig signiert ist, kann es nicht halten 255
.
Im C-Standard heißt es, dass ein Überlauf von vorzeichenlosen Ganzzahlen zu einem Überlauf führt, aber ein Überlauf von vorzeichenbehafteten Ganzzahlen ist undefiniert. Ist diese Zuweisung also undefiniertes Verhalten? Und warum verhält sich gcc so?
Fazit: Das Ergebnis ist implementierungsbedingt und sehr wahrscheinlich -1
aber es ist kompliziert, zumindest im Prinzip.
Die Regeln bezüglich des Überlaufs unterscheiden sich für Operatoren gegenüber Konvertierungen und für Typen mit Vorzeichen gegenüber Typen ohne Vorzeichen – und die Konvertierungsregeln wurden zwischen C90 und C99 geändert.
Ab C90 hat der Überlauf eines Operators mit vorzeichenbehafteten Integer-Operanden (“Überlauf”, was bedeutet, dass das mathematische Ergebnis nicht im Typ des Ausdrucks dargestellt werden kann) ein undefiniertes Verhalten. Für vorzeichenlose Integer-Operanden ist das Verhalten gut definiert als der übliche Wraparound (genau genommen bezeichnet der Standard dies nicht als “Überlauf”). Aber deine Aussage:
char foo = 255;
verwendet keine Operatoren (the =
ist ein Initialisierer, keine Zuweisung), also trifft das in diesem Fall nicht zu.
Wenn Typ char
kann den Wert darstellen 255
(was sowohl für plain gilt char
vorzeichenlos ist oder wenn CHAR_BIT >= 9
), dann ist das Verhalten natürlich wohldefiniert. Das int
Ausdruck 255
wird implizit umgewandelt in char
. (Seit CHAR_BIT >= 8
ist es in diesem speziellen Fall nicht möglich, einen unsignierten Wraparound aufzurufen.)
Andernfalls ergibt die Konvertierung ein Ergebnis, das nicht in a gespeichert werden kann char
.
Ab C90 ist das Ergebnis der Konvertierung implementierungsdefiniert – was bedeutet, dass es garantiert gesetzt wird foo
zu etwas Wert innerhalb des Typbereichs char
, und Sie können diesen Wert ermitteln, indem Sie die Dokumentation der Implementierung lesen, die erforderlich ist, um Ihnen mitzuteilen, wie die Konvertierung funktioniert. (Ich habe noch nie eine Implementierung gesehen, bei der der gespeicherte Wert etwas anderes ist als -1
aber prinzipiell ist jedes Ergebnis möglich.)
C99 hat die Definition geändert, so dass eine überlaufende Konvertierung in einen signierten Typ erfolgt entweder liefert ein implementierungsdefiniertes Ergebnis oder löst ein implementierungsdefiniertes Signal aus.
Entscheidet sich ein Compiler für letzteres, muss er dokumentieren, welches Signal ausgelöst wird.
Was passiert also, wenn ein implementierungsdefiniertes Signal ausgelöst wird? Abschnitt 7.14 des Standards sagt:
Der vollständige Satz von Signalen, ihre Semantik und ihre Standardbehandlung ist implementierungsdefiniert
Es ist (mir) nicht ganz klar, wie groß der Bereich möglicher Verhaltensweisen für die “Standardbehandlung” von Signalen ist. Ich nehme an, im schlimmsten Fall könnte ein solches Signal das Programm beenden. Du könnte oder auch nicht in der Lage sein, einen Signal-Handler zu definieren, der das Signal abfängt.
7.14 sagt auch:
Wenn und wann die Funktion zurückkehrt, wenn der Wert von sig ist SIGFPE,
SIEGEL, SIGSEGVoder jeder andere implementierungsdefinierte Wert, der einer Berechnungsausnahme entspricht, ist das Verhalten undefiniert; andernfalls setzt das Programm die Ausführung an der Stelle fort, an der es unterbrochen wurde.
aber ich nicht denken das gilt, da eine überlaufende Konversion keine “rechentechnische Ausnahme” im hier verwendeten Sinne ist. (Es sei denn, das implementierungsdefinierte Signal ist zufällig SIGFPE
, SIGILL
oder SIGSEGV
— aber das wäre albern).
Wenn also eine Implementierung entscheidet, als Reaktion auf eine überlaufende Konvertierung ein Signal auszulösen, ist das Verhalten (nicht nur das Ergebnis) zumindest implementierungsdefiniert, und es kann Umstände geben, unter denen es undefiniert sein könnte. Jedenfalls scheint es keine zu geben tragbar Umgang mit einem solchen Signal.
In der Praxis habe ich noch nie von einer Implementierung gehört, die die neuen Formulierungen in C99 nutzt. Bei allen Compilern, von denen ich gehört habe, ist das Ergebnis der Konvertierung implementierungsdefiniert – und liefert sehr wahrscheinlich das, was Sie von einer 2er-Komplement-Kürzung erwarten würden. (Und ich bin überhaupt nicht davon überzeugt, dass diese Änderung in C99 eine gute Idee war. Nicht zuletzt hat sie diese Antwort etwa dreimal so lange gemacht, wie sie sonst hätte sein müssen.)
Ein vorzeichenbehafteter Integerüberlauf verursacht nur dann ein undefiniertes Verhalten, wenn er bei der Auswertung von Zwischenergebnissen arithmetischer Ausdrücke auftritt, z. B. während einer binären Mutiplikation, einer unären Dekrementierung usw. Auch die Konvertierung eines Gleitkommawerts in einen ganzzahligen Typ führt zu einem undefinierten Verhalten, wenn der Wert außerhalb des zulässigen Bereichs liegt.
Ein Überlaufen Integral- Die Konvertierung in einen vorzeichenbehafteten Typ verursacht kein undefiniertes Verhalten. Stattdessen erzeugt es ein implementierungsdefiniertes Ergebnis (mit der Möglichkeit, dass ein implementierungsdefiniertes Signal ausgelöst wird).
Gemäß C11, 6.3.1.3:
Wenn ein Wert mit ganzzahligem Typ in einen anderen ganzzahligen Typ als konvertiert wird _Bool
wenn […] der neue Typ ist signiert und der Wert kann darin nicht dargestellt werden; Entweder ist das Ergebnis implementierungsdefiniert oder es wird ein implementierungsdefiniertes Signal ausgelöst.
Irgendwo habe ich gelesen, dass nur wirklich über-fließt waren UB und als solches hat der Zuweisungsoperator kein undefiniertes Verhalten aufgerufen, aber ich bin mir nicht sicher. +1 für diese Frage sowieso.
– Benutzer529758
20. September 2013 um 17:32 Uhr
Aber haben Sie versucht zu drucken
foo
danach? Welchen Wert bekommst du?– smac89
20. September 2013 um 17:36 Uhr
Die Warnung soll Sie nur wissen lassen, dass die Nummer nicht die ist, auf die Sie sie eingestellt haben, anstatt vor undefiniertem Verhalten zu warnen, aber die allgemeinere Frage ist interessant. Meine Intuition sagt, dass es undefiniert ist (da
foo
ist signiert, also wäre es zur Laufzeit undefiniert), aber ich habe keine Ahnung.– David
20. September 2013 um 17:36 Uhr
@Dave Welche Zeile in dieser Antwort sagt, dass es undefiniert ist?
– Benutzer529758
20. September 2013 um 17:40 Uhr
@Smac89: Es gibt `Müll’ aus, weil es in ASCII-Zeichen kein solches Zeichen gibt, aber Sie könnten solche Werte verwenden, um ein Unicode-Zeichen zu erstellen
– Benutzer1042840
20. September 2013 um 18:09 Uhr