Ist “char foo = 255” undefiniertes Verhalten, wenn char signiert ist?

Lesezeit: 7 Minuten

Benutzeravatar von user1042840
Benutzer1042840

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 -pedanticgcc 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?

  • 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

Benutzeravatar von Keith Thompson
Keith Thompson

Fazit: Das Ergebnis ist implementierungsbedingt und sehr wahrscheinlich -1aber 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 >= 8ist 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 -1aber 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, SIGILLoder 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.)

  • Also dies: “char foo = 255” ist eine auf POSIX-Systemen definierte Implementierung, aber in der Praxis läuft es auf Wraparound hinaus. Ist es das gleiche für vorzeichenlose Typen wie in “unsigned int foo = 9999999999”? Ich weiß, dass es Wraprounds gibt, aber ist es auch die Implementierung definiert? Und warum warnt gcc auch vor “Überlauf” im Zeichenfall mit -pedantic-Modus, wenn es kein Überlauf ist, was passiert hier?

    – Benutzer1042840

    20. September 2013 um 19:04 Uhr


  • Es ist meiner Meinung nach überhaupt nicht dumm, dass das Signal ist SIGFPE – während die meisten realen Systeme kein Signal für einen Integer-Überlauf auslösen, sie tun erhöhen oft eine für die ganzzahlige Division, und die, die sie erheben, ist SIFGPE. Ungeachtet der Tatsache, dass der Name a impliziert Fließkommazahl Error. (Natürlich ist das Ergebnis der ganzzahligen Division ein undefiniertes Verhalten)

    – Random832

    20. September 2013 um 19:31 Uhr


  • @user1042840: Die Konvertierung in vorzeichenlose Typen ist durch den Standard definiert; es verwirft effektiv alle außer den niederwertigen N Bits des Ergebnisses (obwohl der Standard das Ergebnis in Form von arithmetischen Werten und nicht von Bits definiert). Warum sagst du, es ist kein Überlauf? (Der C-Standard definiert diesen Begriff nicht.)

    – Keith Thompson

    20. September 2013 um 19:46 Uhr

  • @ Keith Thompson: Ich wurde von dem vorgeschlagen, was Adam Rosenfield sagte: “Außerdem tritt hier kein Überlauf auf – alles, was Sie haben, ist ein Integer-Literal (255, vom Typ int, das nicht überläuft), das in den Typ char konvertiert wird”. Aber jetzt sehe ich, dass Sie darüber gesprochen haben, wie Überlauf in Konvertierungen und Ausdrücken behandelt wird.

    – Benutzer1042840

    20. September 2013 um 19:51 Uhr

  • @Keith Thompson: Zusammenfassend: 1. Bei Konvertierungen ist der vorzeichenbehaftete Überlauf implementierungsdefiniert (oder löst ein implementierungsdefiniertes Signal in C99 aus), ein unsignierter Überlauf führt zu einem Wraparound. 2. In Operatoren ist ein vorzeichenbehafteter Überlauf ein undefiniertes Verhalten (beschrieben in Abschnitt 6.5 in Punkt 5) und ein vorzeichenloses Verhalten führt zu einem Umlauf. Habe ich es richtig verstanden?

    – Benutzer1042840

    20. September 2013 um 20:47 Uhr


AnT steht mit Russlands Benutzer-Avatar
AnT steht zu Russland

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).

  • Können Sie mir sagen, wie Sie sich darauf verlassen?

    – Benutzer1042840

    20. September 2013 um 18:22 Uhr


Gemäß C11, 6.3.1.3:

Wenn ein Wert mit ganzzahligem Typ in einen anderen ganzzahligen Typ als konvertiert wird _Boolwenn […] 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.

  • @Hacks: Was meinst du? 🙂

    – Kerrek SB

    20. September 2013 um 17:41 Uhr


  • Und vermutlich ist das Verhalten eines Programms, wenn es ein “implementierungsdefiniertes Signal” empfängt, undefiniert. (Die Signalklausel wurde von C99 hinzugefügt; ich kenne keine tatsächlichen Implementierungen dafür, und ich bin skeptisch, ob es eine gute Idee war.)

    – Keith Thompson

    20. September 2013 um 17:47 Uhr

  • @KeithThompson In diesem Fall bin ich noch glücklicher, da der Code, den ich mit der Umwandlung von vorzeichenlos in vorzeichenbehaftet schreibe, in C89 geschrieben ist.

    Benutzer529758

    20. September 2013 um 17:51 Uhr

  • @Kerre SB: Das stimmt, aber in n1256.pdf in 3.4.3 heißt es: “Ein Beispiel für undefiniertes Verhalten ist das Verhalten bei Ganzzahlüberlauf”. Würde der Standard zwei verschiedene Dinge zum selben Thema sagen? Vielleicht ist mein Verständnis von Überlauf falsch.

    – Benutzer1042840

    20. September 2013 um 18:29 Uhr


  • Aber wenn gcc vor “Überlauf bei impliziter konstanter Konvertierung” warnt, denke ich das ist ein Überlauf.

    – Benutzer1042840

    20. September 2013 um 18:43 Uhr

1395020cookie-checkIst “char foo = 255” undefiniertes Verhalten, wenn char signiert ist?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy