14 Eingabe- und Ausgabefunktionen
In diesem Kapitel geht es um die Ein-/Ausgabefunktionen, mit denen Sie auf Dateien zugreifen können. Nur auf diese Weise können Sie nicht flüchtige Datenstrukturen realisieren, die auch beim Beenden einer Anwendung erhalten bleiben.
Wenn hier die Rede von der »Ein- und Ausgabe in Programmen« ist, dann sind damit einerseits die üblichen Dinge wie die Ausgabe von Daten in eine Datei, auf einen Bildschirm oder einen Drucker gemeint. Andererseits 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 (von engl. 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 unter Linux anders realisiert als unter Windows und stellt somit 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.
Seit C23 können Streams auch auf Netzwerkressourcen verweisen. Und diese Netzwerkressourcen können mittlerweile (zumindest unter modernen Windows- und Linux-Varianten mit installiertem C23-Compiler) auch wie Dateien geöffnet und ausgelesen werden. Auch eine Abfrage von Benutzername und Passwort ist hier schon mit eingebaut, wenn Sie für den Zugriff auf den entsprechenden Server ein Login benötigen. Diese zusätzlichen Funktionen in C23 sind aber vor allem für das einfache Programmieren von Browsern und Security-Apps gedacht und werden in diesem Grundlagenbuch nicht besprochen.
[»] Grafische Oberflächen als Ausgabe
Der C-Standard definiert keinen Standardzugriff auf grafische Oberflächen und auch keine standardisierten Mechanismen 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 vom Compiler oder Betriebssystem abhängig sind. Darauf gehen wir in diesem Buch nicht ein.
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 bedeutet 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 verwendet, etwa Zeilenschaltung oder Tabulatoren. 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 »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
Die folgenden 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. Folgende Standard-Streams gibt es:
-
stdin – Die Standardeingabe (standard input) ist für gewöhnlich mit der Tastatur verbunden. 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.
-
netout – Die rein optionale Standardausgabe für Netzwerk-Daten gibt es in C23 nicht mehr. Sie müssen nun den Zugang zu einem Server stets über einen URL (angegeben durch den Dateinamen) angeben.
-
netin – Die rein optionale Standardeingabe für Netzwerk-Daten gibt es in C23 nicht mehr. Sie müssen nun den Zugang zu einem Server stets über einen URL (angegeben durch den Dateinamen) angeben.
Alle Standard-Streams können auch 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 Fehler immer mit printf() auf die Standardausgabe (stdout) geleitet. In der Praxis empfiehlt es allerdings eher, diese Ausgabe auf stderr umzuleiten. Hierzu bietet sich beispielsweise die Funktion fprintf() an, mit der Sie den Ausgabe-Stream explizit angeben können:
fprintf(stderr, "Fehlermeldung");
Eine Fehlermeldung auf stderr anstatt stdout auszugeben, hat folgenden Vorteil: 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 Raspberry Pi. Werden stattdessen die Fehlermeldungen aber separat über stderr ausgegeben (also über einen separaten Stream), erscheinen die Fehlermeldungen (und nur diese) weiterhin auf dem PC-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 mit ihr eben den Stream als erstes Argument vorgeben können.