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
0
bei Erfolg,EOF
im 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
size
die Größe des verarbeiteten Objektes (sizeof(x)
)buf
der Puffer der geschrieben/in den gelesen wirdn
die 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ürwhence
SEEK_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 vonstdin
int fgetc(FILE *in)
List das nächste Zeichen aus Dateiin
int putchar(int c)
Schreibt ein Zeichen aufstdout
int 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
in
inbuf
- kehrt bei
'\n'
zurück oder wennsize - 1
Zeichen gelesen wurden '\n'
selbst wird auch zurückgegeben- gibt Zeiger auf
buf
zurück oderNULL
im Fehlerfall - auf keinen Fall
gets(char*)
verwenden ⇒ Buffer-Overflow int
fputs
(const char *str, FILE *out)
- schreibt den String
str
in die Dateiout
- stoppt beim
'\0'
- gibt die Anzahl der geschriebenen Zeichen zurück, oder
EOF
bei 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.
int
fscanf
(FILE *in, const char *format, ...)
Analog zuscanf
aber für Dateiint
fprintf
(FILE *out, const char *format, ...)
Analog zusprintf
aber 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