Fehlerbehandlung
Video zum Kapitel

C und Fehlerbehandlung
Wer bisher Java programmiert hat, ist es gewohnt, eine strukturierte Fehlerbehandlung zu bekommen, d. h. ein von der Sprache unterstütztes Konzept, um mit Fehlern umzugehen und sie zu behandeln. C++ und Java bieten Exceptions zur Fehlerbehandlung an. C hat aufgrund seines Alters kein entsprechendes Konzept.
- Das Konzept zur Fehlerbehandlung in C lässt sich kurz zusammenfassen:
Es gibt kein Konzept - Fehler werden durch spezielle Rückgabewerte der Funktionen signalisiert
- Aufrufer muss auf den Rückgabewert prüfen und reagieren
- Fehler behandeln (Fehlerbehandlungsroutine anspringen → goto)
- Fehler ignorieren
- Fehler selbst über einen Rückgabewert weitergeben
- Zusätzlich wird eine globale Variable
errno
gesetzt
Während man bei C die fehlende Fehlerbehandlung auf das Alter schieben kann, stellt sich die Frage, warum eine der modernsten Sprachen, nämlich Go (2007 erschienen), ebenfalls kein Konzept für Ausnahmen hat. Tatsächlich ist es so, dass Ausnahmen ein durchaus umstrittenes Konzept sind und nicht von allen Entwicklerinnen als die einzige Lösung für das Signalisieren von Problemen angesehen wird. Die Entwickler von Go haben sich entschieden, dass Funktionen beliebig viele Rückgabewerte haben können und dass einer davon zum Anzeigen von Fehlern verwendet wird. Sie empfanden Ausnahmen als zu schwergewichtig und fürchteten, dass es sonst wie bei Java zu einer Flut von Ausnahmen kommt, die niemand mehr behandeln kann und will.
Der folgende Code zeigt ein Beispiel dafür, wie in C mit Fehlern umgegangen wird.
if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < O) {
DieWithError("socket () failed") ;
}
/* Bind to the local address */
if (bind(servSock, (struct sockaddr *)&echoServAddr,
sizeof(echoServAddr)) < O) {
DieWithError ("bind () failed");
}
/* Mark the socket so it will listen for incoming connections */
if (listen(servSock, MAXPENDING) < O) {
DieWithError("listen() failed") ;
}
for (;;) { /* Run forever */
if ((clntSock = accept(servSock, (struct sockaddr *) &echoClntAddr,
&clntLen)) < O) { /* Wait for a client to connect */
DieWithError("accept() failed");
}
}
Man sieht hier an dem C-Beispiel sehr deutlich die Probleme klassischer Fehlerbehandlung über Rückgabewerte:
- Die normale Programmlogik und die Fehlerbehandlung sind wild gemischt.
- Der Programmcode wird erheblich aufgebläht und sehr unübersichtlich, da manche IFs der Fehlerbehandlung dienen und andere wieder dem normalen Kontrollfluss.
- Der Rückgabewert von Funktionen ist doppeldeutig: Er transportiert sowohl Fehler- als auch normale Informationen.
- Es kann leicht passieren, dass man vergisst einen Fehlercode abzufragen, sodass Fehler überhaupt nicht behandelt werden.
- Es ist sehr schwer Fehler weiterzureichen, da die hier dargestellte Funktion vier verschiedene Fehlersituationen im eigenen Rückgabewert codieren müsste.
Es gibt einige allgemeine Konventionen, die man in C-Programmen bezüglich der Fehlerbehandlung antrifft.
- Funktionen, die einen Pointer zurückgeben (z. B.
malloc
)
⇒ Fehler wird durchNULL
angezeigt
⇒ Fehlercode muss auserrno
gelesen werden - Funktionen, die positive Werte zurückgeben (z. B.
open
)
⇒ Fehlercode wird durch-1
angezeigt
⇒ Fehlercode muss auserrno
gelesen werden - Funktionen, die normalerweise nichts zurückgeben müssen (z. B.
pthread_create
)
⇒ Fehlercode direkt als Rückgabewert geliefert,0
heißt alles OK char*
strerror(int errnum)
aus<string.h>
⇒ Fehlernummern zu Fehlermeldungvoid
perror(const char *s)
⇒ gibt den Texts
und danach die Fehlerbeschreibung aus
FILE* fh = fopen("/gibtsnicht");
if (!fh) {
perror("Fehler beim Öffnen der Datei");
exit(1);
}
Fehler beim Öffnen der Datei: No such file or directory
Fehlernummern und Fehlerausgabe
- Die Fehlernummern werden über
#define
definiert und sind spezifisch für die aufgerufenen Funktion (siehe<errno.h>
) #define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* Input/output error */
#define ENXIO 6 /* Device not configured */
- …
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv) {
FILE* f;
if ((f = fopen("test", "r")) == NULL) {
printf("%s\n", strerror(errno));
exit(1);
}
}
No such file or directory
Durch Verwendung von strerror
wird der Fehlercode, der beim Versuch die nicht-existente Datei zu öffnen, gesetzt wurde in den Text „No such file or directory“ übersetzt.
Beispiel: Fehler weitergeben
#define EFOPEN 1
FILE* fd = NULL;
int openFile(const char* file) {
fd = fopen(file, "r");
if (fd == NULL) {
return EFOPEN;
}
else {
return 0;
}
}
Dieses C-Programm zeigt, wie man Fehler weitergeben kann. Es definiert eine Konstante EFOPEN
und eine Funktion openFile
, die versucht, eine Datei zu öffnen. Die Konstante namens EFOPEN
wird verwendet, um einen Fehlercode zu repräsentieren, falls die Datei nicht geöffnet werden kann.
Die Funktion openFile
versucht, eine Datei im Lesemodus ("r"
) zu öffnen, wobei der Dateiname als Argument übergeben wird (file
). Wenn fd
NULL
ist (d. h., die Datei konnte nicht geöffnet werden), gibt die Funktion EFOPEN
zurück, um einen Fehler anzuzeigen. Falls die Datei erfolgreich geöffnet wird, gibt die Funktion 0
zurück, um anzuzeigen, dass keine Fehler aufgetreten sind.