Speichern des EOF-Zeichens (End of File) in einem Zeichentyp

11

Ich habe in Dennis Ritchies Buch The C Programming Language gelesen, dass inteine Variable verwendet werden muss, um EOF zu halten - um sie so groß zu machen, dass sie EOF-Werte enthalten kann - nicht char. Der folgende Code funktioniert jedoch einwandfrei:

#include<stdio.h> 

main()  { 
  char c; 
  c=getchar(); 
  while(c!=EOF)  { 
    putchar(c); 
    c=getchar(); 
  } 
} 

Wenn keine Eingabe mehr erfolgt, wird getcharEOF zurückgegeben. Und im obigen Programm kann die Variable cmit dem Typ char sie erfolgreich halten.

Warum funktioniert das? Gemäß der Erklärung in dem oben erwähnten Buch sollte der Code nicht funktionieren.

user1369975
quelle
5
Dieser Code schlägt wahrscheinlich fehl, wenn Sie ein Zeichen mit dem Wert lesen 0xff. Das Speichern des Ergebnisses getchar()in intlöst dieses Problem. Ihre Frage entspricht im Wesentlichen der Frage 12.1 in den häufig gestellten Fragen zu comp.lang.c. Dies ist eine hervorragende Ressource. (Sollte auch main()sein int main(void), und es würde nicht schaden, return 0;vor dem Abschluss ein hinzuzufügen }.)
Keith Thompson
1
@delnan: Der verlinkte Artikel ist nicht ganz richtig darüber, wie Unix Control-D behandelt. Der Eingabestream wird nicht geschlossen. Es bewirkt lediglich, dass fread (), das auf der Konsole blockiert ist, sofort mit noch nicht gelesenen Daten zurückkehrt. Viele Programme interpretieren eine Null-Byte-Rückgabe von fread () als Hinweis auf EOF, aber die Datei bleibt tatsächlich offen und kann mehr Eingaben liefern.
Supercat

Antworten:

11

Ihr Code scheint zu funktionieren, da die impliziten Typkonvertierungen versehentlich das Richtige bewirken.

getchar()Gibt ein intmit einem Wert zurück, der entweder in den Bereich von passt unsigned charoder ist EOF(was negativ sein muss, normalerweise ist es -1). Beachten Sie, dass es EOFsich nicht um ein Zeichen handelt, sondern um ein Signal, dass keine weiteren Zeichen verfügbar sind.

Beim Speichern des Ergebnisses von getchar()in cgibt es zwei Möglichkeiten. Entweder kann der Typ charden Wert darstellen. In diesem Fall ist dies der Wert von c. Oder der Typ char kann den Wert nicht darstellen. In diesem Fall ist nicht definiert, was passieren wird. Intel-Prozessoren hacken nur die hohen Bits ab, die nicht in den neuen Typ passen (wodurch der Wert von Modulo 256 effektiv reduziert wird char), aber darauf sollten Sie sich nicht verlassen.

Der nächste Schritt ist der Vergleich cmit EOF. Als EOFein ist int, cwird ein überführt werden intals auch, in den gespeicherten Wert zu bewahren c. Wenn cder Wert von gespeichert werden kann EOF, ist der Vergleich erfolgreich. Wenn der Wert cjedoch nicht gespeichert werden kann, schlägt der Vergleich fehl, da bei der Konvertierung EOFin den Typ ein nicht behebbarer Informationsverlust aufgetreten ist char.

Es scheint, dass Ihr Compiler den charTyp signiert und den Wert EOFklein genug gemacht hat, um hinein zu passen char. Wenn Sie charnicht signiert wären (oder wenn Sie verwendet hätten unsigned char), wäre Ihr Test fehlgeschlagen, da unsigned charder Wert von nicht gehalten werden kann EOF.


Beachten Sie auch, dass es ein zweites Problem mit Ihrem Code gibt. Da EOFes sich nicht um ein Zeichen selbst handelt, sondern Sie es in einen charTyp zwingen , gibt es sehr wahrscheinlich ein Zeichen, das als solches falsch interpretiert wird, EOFund für die Hälfte der möglichen Zeichen ist es undefiniert, ob sie korrekt verarbeitet werden.

Bart van Ingen Schenau
quelle
Zwingen zu geben charWerte außerhalb des Bereichs CHAR_MIN.. CHAR_MAXwird benötigt , entweder eine Implementierung definierten Wert zu ergeben, einen Bitmuster ergeben , die die Implementierung definiert als Falle Darstellung oder ein implementierungsspezifischen Signal erhöhen. In den meisten Fällen müssten Implementierungen viel zusätzliche Arbeit auf sich nehmen, um etwas anderes als die Reduzierung des Zweierkomplements zu tun. Wenn die Mitglieder des Normungsausschusses der Idee
zustimmen,
... Ich würde solchen Zwang als zuverlässig betrachten (um nicht zu sagen, dass Code seine Absichten nicht dokumentieren sollte, aber das (signed char)xsollte als klarer und genauso sicher angesehen werden wie ((unsigned char)x ^ CHAR_MAX+1))-(CHAR_MAX+1).) Wie es ist, sehe ich keine Wahrscheinlichkeit dafür Compiler, die jedes andere Verhalten implementieren, das dem heutigen Standard entspricht; Die einzige Gefahr wäre, dass der Standard geändert wird, um das Verhalten im angeblichen Interesse der "Optimierung" zu brechen.
Supercat
@supercat: Der Standard ist so geschrieben, dass kein Compiler Code erzeugen muss, dessen Verhalten von dem Prozessor, auf den er abzielt, natürlich nicht unterstützt wird. Der größte Teil des undefinierten Verhaltens ist vorhanden, da sich (zum Zeitpunkt des Schreibens des Standards) nicht alle Prozessoren konsistent verhalten haben. Mit zunehmender Reife der Compiler haben Compiler-Autoren begonnen, das undefinierte Verhalten zu nutzen, um aggressivere Optimierungen vorzunehmen.
Bart van Ingen Schenau
In der Vergangenheit war die Absicht des Standards größtenteils so, wie Sie es beschreiben, obwohl der Standard einige Verhaltensweisen so detailliert beschreibt, dass Compiler für einige gängige Plattformen mehr Code generieren müssen, als unter einer lockeren Spezifikation erforderlich wäre. Der Typ Zwang in int i=129; signed char c=i;ist ein solches Verhalten. Relativ wenige Prozessoren haben eine Anweisung, die cgleich ist, iwenn sie im Bereich von -127 bis +127 liegt, und eine konsistente Zuordnung anderer Werte von izu Werten im Bereich von -128 bis +127 ergibt, die sich von der Zweierkomplementreduktion unterscheiden, oder. ..
Supercat
... würde in solchen Fällen konsequent ein Signal auslösen. Da der Standard verlangt, dass Implementierungen entweder eine konsistente Zuordnung liefern oder ein Signal konsistent auslösen, wären die einzigen Plattformen, auf denen der Standard Raum für etwas anderes als die Reduzierung des Zweierkomplements lassen würde, Dinge wie DSPs mit sättigungsarithmetischer Hardware. Was die historische Grundlage für undefiniertes Verhalten betrifft, würde ich sagen, dass das Problem nicht nur bei Hardwareplattformen liegt. Selbst auf einer Plattform, auf der sich der Überlauf sehr konsistent verhält, kann es nützlich sein, ihn von einem Compiler
abfangen zu