14 Eingabe- und Ausgabefunktionen
In diesem Kapitel geht es um Ein- und Ausgabefunktionen, mit denen Sie auf Dateien zugreifen können. Erst hierdurch können Sie Anwendungen erstellen, die mit nicht-flüchtigen Daten arbeiten, die auch beim Beenden des Programms nicht verloren gehen. Ist hier die Rede von der Ein- und Ausgabe in Programmen, sind damit einerseits die üblichen Dinge wie die Ausgabe von Daten in eine Datei, auf einen Bildschirm oder Drucker gemeint, auf der anderen Seite geht es natürlich um die Möglichkeit, Daten aus einer Datei oder von der Tastatur lesen zu können. Für die Ein- und Ausgabe stellt die Standardbibliothek von C die nötigsten Funktionen zur Verfügung. Sie sind alle in der Header-Datei stdio.h deklariert.
14.1 Verschiedene Streams und Standard-Streams
Die Ein- und Ausgabe von Daten in C wird über das Konzept der Streams (data stream = Datenstrom) realisiert. Beim Öffnen einer Datei wird z. B. für gewöhnlich ein neuer Datei-Stream (bzw. File-Pointer oder Dateizeiger) angelegt und beim Schließen wieder entfernt. Die einzelnen Streams werden als Ressource vom Betriebssystem verwaltet, auf dem das Programm ausgeführt wird.
Ein Stream ist allerdings ein rein abstrakter Begriff, der auf verschiedenen Systemen verschieden realisiert wird. So ist z. B. der eine Datei beschreibende FILE*-Deskriptor auf Linux anders realisiert, als auf Windows und stellt nur nach außen hin einen unter C vereinheitlichten Zugang zu Dateien her. Genau so verhält es sich mit den Low-Level-Dateifunktionen und auch mit der Standardeingabe stdin.
Grafische Oberflächen als Ausgabe
Der C-Standard definiert keinen Standardzugriff auf grafische Oberflächen oder zu deren Interaktion mit dem Anwender. Natürlich gibt es jenseits des C-Standards auch viele andere Programmierschnittstellen in C für Anwendungsprogramme und Betriebssystem-APIs, die Sie für die Programmierung verwenden können. Allerdings sind dies meistens Bibliotheken, die von Compiler oder Betriebssystem abhängig sind. Darauf wird in diesem Buch nicht eingegangen.
14.1.1 Streams im Textmodus
Ein Stream im Textmodus liest und schreibt einzelne Zeichen eines Textes. Hier sind die einzelnen Textzeichen keine Bytes und dürfen auch nicht mit diesen verwechselt werden – natürlich heißt dies auch, dass manche Byte-Werte im Textmodus nicht verarbeitet werden können. Wenn ein Stream an eine Datei gebunden wird, werden diese Zeichen aus der Datei gelesen oder in diese geschrieben. Für gewöhnlich wird dieser Text zusätzlich in einzelne Zeilen aufgeteilt. Bei solchen Streams werden alle sichtbaren Zeichen und einige Steuercodes, etwa Zeilenschaltung oder Tabulatoren, verwendet. Da bei Windows-Systemen das Zeilenende oft mit \r\n ausgegeben wird und Linux-/Unix-Systeme dafür nur \n verwenden, führt der Compiler hier eine automatische Konvertierung durch. Dies müssen Sie unbedingt beachten, wenn Sie z. B. einen Linux-Text unter Windows öffnen wollen.
Um diese Konvertierung müssen Sie sich aber in den folgenden Beispielen nicht kümmern. Das Ende eines Textes wird für gewöhnlich durch das Steuerzeichen ^Z (Zeichencode 26) angezeigt (das auch mit der Tastenkombination (Strg) + (Z) unter Windows oder (Strg) + (D) unter Linux/UNIX »ausgelöst« werden kann). Hier müssen Sie allerdings aufpassen, denn nach dem Textende-Zeichen können keine Zeichen mehr in den Stream geschrieben werden, weil Sie danach beim Lesen schlicht ignoriert werden.
14.1.2 Streams im binären Modus
Bei einem Stream im binären Modus wird nicht mehr auf den Inhalt wie einzelne Zeilen, Zeichen oder Sonderzeichen geachtet, sondern die Daten werden Byte für Byte verarbeitet. Das heißt natürlich auch, dass Sie in diesem Modus durchaus über das Textende-Zeichen hinaus lesen können. Daher stehen Daten, die auf einem bestimmten System in einem binären Modus geschrieben wurden, auf demselben System beim Lesen auch exakt so wieder zur Verfügung. Es werden keinerlei automatische Konvertierungen durchgeführt. Natürlich müssen Sie sich dann um das korrekte Datenformat selbst kümmern.
14.1.3 Standard-Streams
Drei Streams, die Standard-Streams, sind bei jedem C-Programm von Anfang an vorhanden. Bei den Standard-Streams handelt es sich um Zeiger auf ein FILE-Objekt. Nachfolgend sehen Sie die Standard-Streams:
-
stdin – Die Standardeingabe (standard input), die für gewöhnlich mit der Tastatur verbunden ist. Der Stream ist zeilenweise gepuffert.
-
stdout – Die Standardausgabe (standard output) ist mit dem Bildschirm zur Ausgabe verbunden. Auch die Standardausgabe wird zeilenweise gepuffert.
-
stderr – Die Standardfehlerausgabe (standard error output) ist wie stdout ebenfalls mit dem Bildschirm verbunden, aber die Ausgabe erfolgt ungepuffert.
Alle drei Standard-Streams können umgelenkt werden. Die Umlenkung kann dabei auch programmtechnisch mit der Standardfunktion freopen() durchgeführt werden oder über die Umgebung des Programms, beispielsweise mit einem Umleitungszeichen in der Kommandozeile.
Die Standardfehlerausgabe
Bei den bisherigen Beispielen haben Sie immer eine Fehlerausgabe mit printf() auf die Standardausgabe (stdout) gemacht. In der Praxis ist es allerdings empfehlenswerter, diese Ausgabe auf stderr auszugeben. Hierzu bietet sich beispielsweise die Funktion fprintf() an, mit der Sie den Ausgabe-Stream explizit angeben können:
fprintf(stderr, "Fehlermeldung");
Der Vorteil, eine Fehlermeldung auf stderr anstatt stdout auszugeben ist: Wenn die gewöhnliche Standardausgabe umgelenkt wird, werden die Fehlermeldungen, die Sie über stdout ausgeben, nicht mehr direkt auf dem PC-Bildschirm angezeigt, sondern z. B. auf dem Pi. Werden stattdessen die Fehlermeldungen separat über stderr ausgegeben (also über einen separaten Stream), erscheinen die Fehlermeldungen (und nur diese) weiterhin auf dem Bildschirm. Ferner können Sie auch die Fehlermeldungen separat umlenken, z. B. auf einen Server oder in eine Logdatei. Ansonsten funktioniert die Funktion fprintf() genauso wie schon printf(), nur dass Sie damit eben den Stream als erstes Argument vorgeben können.