Input/Output mit stdio.h
Video zum Kapitel
stdio-Bibliothek
In C sind die Eingabe- und Ausgabeoperationen nicht Teil der Sprache, sondern werden durch eine Bibliothek zur Verfügung gestellt. Da diese Bibliothek im C-Standard definiert wird, sollte sie auf allen Plattformen und bei allen C-Compilern zur Verfügung stehen. Ausnahmen sind eingebettete Systeme, die kein Input/Output unterstützen.
Dreh und Angelpunkt der I/O in C ist die stdio-Bibliothek, die eine ganze Reihe von Funktionen anbietet.
- Eingabe/Ausgabe wird über die
stdio-Bibliothek realisiert #include <stdio.h>- wird automatisch gelinkt
- Definiert den Typ
FILE*und Funktionen für Dateizugriff - Definiert
stdin,stdout,stderr
stdin, stdout und stderr sind vordefinierte FILE-Objekte, die den Zugriff auf die Standard-Ein- und -Ausgabe ermöglichen.
Neben den Funktionen in stdio.h, die im C-Standard definiert werden, gibt es eine Reihe von korrespondierenden Low-Level-I/O-Funktionen, die vom POSIX-Standard festgelegt werden. Diese Funktionen sind näher am Betriebssystem und bieten weniger Komfort. Außerdem sind sie nur auf Systemen verfügbar, die den POSIX-Standard unterstützen, wozu allerdings die relevanten Betriebssysteme gehören.
Beispiele Low-Level-Funktionen sind:
int open(const char *pathname, int flags, mode_t mode);int creat(const char *pathname, mode_t mode);int close(int fd);
Ken Thompson, einer der Erfinder von Unix, wurde einmal gefragt, was er heute anders machen würde, wenn er Unix noch einmal entwickeln dürfte. Seine Antwort war „I'd spell creat with an e.“
Die Low-Level-Funktionen werden weiter unten diskutiert.
Dateien öffnen und schließen
Bevor man eine Datei lesen (oder schreiben) kann, muss man sie mit fopen öffnen. Als Rückgabewert bekommt man einen Pointer auf ein FILE-Objekt zurück. Der genaue Inhalt dieses Objektes ist irrelevant, denn es dient ausschließlich dazu, bei den Funktionsaufrufen zu übergeben, auf welche Datei man sich gerade beziehen möchte.
FILE *fopen(const char *path, const char *mode)
- öffnet die Datei
path - Modus (
mode) "r": Lesen"r+": Lesen und schreiben (ab Anfang)"w": Auf 0 Byte kürzen schreiben (ab Anfang)"w+": Auf 0 Byte kürzen, lesen und schreiben (ab Anfang)"a": Schreiben (am Ende anhängen)"a+": Lesen und schreiben (am Ende anhängen)- bei Erfolg wird ein
FILE*zurückgegeben, im FehlerfallNULL
Eine Datei, die man einmal geöffnet hat, muss man nach der Verwendung wieder mit fclose schließen. Ein Prozess kann nur eine bestimmte Anzahl von Dateien geöffnet halten und es kommt zu Fehlern, wenn er diese Grenze überschreitet. Deswegen ist ein sorgfältiger Umgang mit den Ressourcen wichtig und was geöffnet wurde, sollte möglichst bald auch wieder geschlossen werden.
Wenn ein Programm beendet wird, werden zwar automatisch alle geöffneten Dateien geschlossen – es ist aber schlechter Stil, sich hierauf zu verlassen.
int fclose(FILE *stream)
- schließt die Datei
stream 0bei Erfolg,EOFim Fehlerfall
FILE* handle = fopen("/tmp/file", "r");
if (handle == NULL) {
/* Fehler */
exit(1);
}
/* Mit Datei arbeiten */
if (fclose(handle)) {
/* Fehler beim Schließen */
}
Ein Fehler beim Schließen einer Datei ist selten, aber wenn er auftritt auch nur schwer zu behandeln, deswegen wird man normalerweise nicht versuchen, das Schließen zu wiederholen.
Die Standardbibliothek spricht hier von Streams und nicht Dateien, weil die Methoden auch auf andere Arten von I/O angewendet werden können, z. B. die Console, die keine Datei ist.
Lesen und Schreiben von Binärdaten
Wenn man eine Datei erfolgreich geöffnet hat, kann man auf die Daten in der Datei zugreifen. Ob man nur lesen, nur schreiben oder beides kann hängt davon ab, in welchem Modus man die Datei bei fopen geöffnet hat.
Verarbeitet man binäre Daten, werden folgende Funktionen eingesetzt:
size_t fread(void *buf, size_t size, size_t n, FILE *stream)size_t fwrite(const void *buf, size_t size, size_t n, FILE *stream)
Hier ist
sizedie Größe des verarbeiteten Objektes (sizeof(x))bufder Puffer der geschrieben/in den gelesen wirdndie Anzahl der verarbeiteten Objekte der Größesize- Anzahl der gelesenen/geschriebenen Objekte wird zurückgegeben
fread und fwrite basieren auf der Vorstellung, dass man nicht einfach nur Bytes schreibt, sondern auch andere Daten, z. B. int-Werte oder auch Inhalte von Strukturen. Deswegen gibt man auch nicht an, dass man x-Bytes schreibt, sondern n Elemente der Größe size.
Teilweise will man sich nicht nur linear durch die Datei bewegen, sondern auch springen können. Hierzu dient die Funktion fseek, mit der man die Position für die nächste Lese- oder Schreiboperation verschieben kann. Hierbei kann man vom Anfang, vom Ende oder von der aktuellen Position aus das Ziel angeben.
int fseek(FILE *stream, long offset, int whence)
Verschiebt die aktuelle Position in der Datei
Mögliche Werte fürwhenceSEEK_SET: vom Anfang ausSEEK_CUR: von der aktuellen Position ausSEEK_END: vom Ende auslong ftell(FILE *stream)
Liefert die aktuelle Position in der Datei
Mit ftell kann man die aktuelle Position in der Datei auslesen.
/* Datei zum Schreiben im Binärmodus öffnen */
FILE *fh = fopen("/tmp/data", "w+");
/* Fehlerbehandlung */
if (!fh) {
perror("Datei: ");
exit(1);
}
unsigned int magic_value = 0xcafebabe;
fwrite(&magic_value, sizeof(int), 1, fh);
/* An den Anfang springen und Daten lesen */
fseek(fh, 0, SEEK_SET);
unsigned int read_back;
fread(&read_back, sizeof(int), 1, fh);
printf("Aus Datei: %x\n", read_back);
Das Beispiel zeigt, wie eine Datei /tmp/data zum Lesen und Schreiben geöffnet und dann der Integer-Wert 0xcafebabe hineingeschrieben wird. Danach wird der Dateizeiger wieder an den Anfang der Datei geschoben und die Daten werden zurück gelesen und ausgegeben.
Zeichen-I/O
Funktionen für den Zugriff auf einzelne Zeichen. Alle Funktionen geben EOF zurück, wenn Datei/Stream zu Ende ist oder ein Fehler auftritt
int getchar()
Liest das nächste Zeichen vonstdinint fgetc(FILE *in)
List das nächste Zeichen aus Dateiinint putchar(int c)
Schreibt ein Zeichen aufstdoutint fputc(int c, FILE *out)
Schreibt ein Zeichen in Dateiout
Bemerkenswert ist aber die Tatsache, dass die Funktion getchar() ein int als Rückgabetyp hat und kein char, obwohl ein Stream byteweise gelesen wird. Der größere Datentyp ist nötig, da durch ein EOF signalisiert wird, dass der Stream zu Ende ist. EOF hat den Wert -1. Da aber -1 ein gültiger char-Wert ist, hat man hier den größeren Datentyp nehmen müssen. Aus diesem Grund ist es falsch den Rückgabewert von getchar() vor dem Vergleich mit EOF zu casten. Erst muss mit EOF (als int) verglichen werden, dann gecastet, sonst bricht der Lesevorgang möglicherweise zu früh ab, nämlich wenn in den Daten zufällig ein 0xFF vorkommt, das vorzeichenbehaftet einer -1 entspricht.
int c;
FILE* fd = fopen("test.txt", "r");
while ((c = fgetc(fd)) != EOF) {
printf("%c", (unsigned char) c);
}
Dass die putchar()- und fputc()-Funktionen auch einen int und kein unsigned char nehmen, dient der Bequemlichkeit des Programmierers, der sich so teilweise einen Cast ersparen kann.
Zeilen-I/O
Anstatt einzelne Zeichen kann man auch direkt auf Zeilen arbeiten. Die entsprechenden Funktionen akzeptieren bzw. liefern Zeichenketten in Form von char*.
Funktionen für den Zugriff auf Zeilen
char *fgets(char *buf, int size, FILE *in)- liest die nächste Zeile von
ininbuf - kehrt bei
'\n'zurück oder wennsize - 1Zeichen gelesen wurden '\n'selbst wird auch zurückgegeben- gibt Zeiger auf
bufzurück oderNULLim Fehlerfall - auf keinen Fall
gets(char*)verwenden ⇒ Buffer-Overflow intfputs(const char *str, FILE *out)- schreibt den String
strin die Dateiout - stoppt beim
'\0' - gibt die Anzahl der geschriebenen Zeichen zurück, oder
EOFbei Fehler
Die Funktion gets ist so gefährlich, dass sie inzwischen aus dem Standard entfernt wurde und der Compiler bei ihrer Verwendung entsprechende Fehlermeldungen erzeugt.
Formatierte I/O
Analog zu den Funktionen scanf, sprintf etc., mit denen man Daten in Strings oder Strings in andere Datentypen umwandeln kann, gibt es entsprechende Funktionen auch für Eingabe- und Ausgabe. Anstatt des Strings ist hier die Datei die Quelle bzw. das Ziel der Daten.
intfscanf(FILE *in, const char *format, ...)
Analog zuscanfaber für Dateiintfprintf(FILE *out, const char *format, ...)
Analog zusprintfaber für Datei
fscanf(...) sollte man nicht verwenden. Besser fgets(str, ...) zusammen mit sscanf(str, ...) Von der Console lesen
Ein Spezialfall der Ein- und Ausgabe ist der Zugriff auf die Konsole.
- Mit
fgets(str, ...)undsscanf(str, ...)kann man Daten von der Konsole lesen
int main(int argc, char** argv) {
char buffer[255];
int zahl;
printf("Bitte geben Sie eine Zahl ein: ");
if (fgets(buffer, 254, stdin) != 0) {
sscanf(buffer, "%d", &zahl);
printf("Die Zahl war %d\n", zahl);
}
}
Bitte geben Sie eine Zahl ein: 62
Die Zahl war 62