Forum: PC-Programmierung C: Fehlerbehandlung und -weiterleitung


von dotm (Gast)


Lesenswert?

Hallo!
Eventuell ist darüber schon viel geschrieben worden, ich hab mir aber 
auch selbst den Kopf schon viel zerbrochen und die Lösungen aus dem 
Internet passen irgendwie nicht auf meine Bedürftnisse.
Ich möchte ein Fehler/Statusbehandlungssystem in C implementieren.
Ich verwende den STM32F373 Cortex M4 mit CMISIS-RTOS.
Das erste Fehlerstatuskonstrukt das mir (vor langer Zeit) über den Weg 
gelaufen ist, kam von ST:
1
typedef enum
2
  {
3
  ERROR = 0,
4
  SUCCESS = !ERROR
5
  } ErrorStatus;
Prinzipiell ok, da viele Funktionen 0 als Error ausgeben, wenn man aber 
bei eigenen Funktionen nie einen Wert retourniert sondern alles in 
Pointer schreibt:
1
ErrorStatus Funktion (const uint8_t eingabewert, uint8_t *ausgabewert);
Dann ist obige Struktur unzureichend. Grund: was für ein Fehler 
aufgetreten ist, kann man nicht weiter spezifizieren.
Meine Idee war es nun den Fehler sozusagen durchzureichen. Die Methode 
mit setjmp ist für mich nicht anwendbar, da einige Funktionen auf jeden 
Fall deinitialisieren müssen.
Ich möchte auch die Deinitialisierungsroutinen nicht jedesmal vor einem 
Return dazuschreiben, daher dachte ich an so ein Konstrukt:
1
typedef uint16_t ErrorStatusMessage;
2
#define CHECKERROR(x) if ((x) != 0) goto freturn;
3
4
ErrorStatusMessage FunktionEins (const uint8_t eingabewert, uint8_t *ausgabewert)
5
  {
6
  ErrorStatusMessage error_status_message = 0;
7
  // Initialisierungsfunktionen:
8
  SetMutex(funktion_eins_mutex);
9
  
10
  // Anfang Anweisungen
11
  error_status_message = FunktionZwei();
12
  CHECKERROR(error_status_message)
13
  
14
  // hier werden noch weitere Funktionen oder Anweisungen durchgeführt
15
  
16
  freturn:
17
  // Muss vor return auf jeden Fall ausgeführt werden
18
  SetMutex(funktion_eins_mutex);
19
  return error_status_message;
20
  }
Der Grund warum ich goto verwende und nicht break, ist dass ich auch in 
einer Schleife, oder in einer switch-Anweisung zu freturn springen kann.
In dem Fall wäre nun SUCCESS = 0 und jede Fehlermeldung würde immer 
komplett durchgereicht werden.
Blöderweise müsste ich dann noch sicherstellen, dass alle 
Fehlermeldungen eine eindeutige Zahl haben, sonst nützt mir eine 
durchgereichte Fehlermeldung recht wenig.
Ich hab mir also folgende Erweitung überlegt:
1
#include "erroroffsets.h"
2
typedef uint16_t ErrorStatusMessage;
3
#define CHECKERROR(x) if ((x) != 0) goto freturn;
4
#define MKERROR(x)  ((x) + ERROR_OFFSET) 
5
6
#ifdef ERROR_SPEZIALFUNKTIONEN_OFFSET
7
#define ERROR_OFFSET ERROR_SPEZIALFUNKTIONEN_OFFSET
8
#else
9
#define ERROR_OFFSET 0
10
#endif
11
12
#define FUNKTION_EINS_FEHLER_WERTZUKLEIN MKERROR(1) 
13
ErrorStatusMessage SpezialFunktionEins (const uint8_t eingabewert, uint8_t *ausgabewert)
14
  {
15
  ErrorStatusMessage error_status_message = 0;
16
  uint8_t wert;
17
  // Initialisierungsfunktionen:
18
  SetMutex(funktion_eins_mutex);
19
  
20
  // Anfang Anweisungen
21
  error_status_message = FunktionZwei (&wert);
22
  CHECKERROR(error_status_message)
23
  
24
  // hier wird irgendwas berechnet
25
  if (wert < 100)
26
    {
27
    error_status_message = FUNKTION_EINS_FEHLER_WERTZUKLEIN;
28
    goto freturn;
29
    }
30
  
31
  freturn:
32
  // Muss vor return auf jeden Fall ausgeführt werden
33
  SetMutex(funktion_eins_mutex);
34
  return error_status_message;
35
  }
Man kann nun in erroroffsets.h jedem modul einen offset geben:
1
//datei erroroffsets.h
2
#define ERROR_SPEZIALFUNKTIONEN_OFFSET 0x0100
Wenn erroroffsets korrekt gewartet ist, dann ist jede Fehlermeldung in 
jedem Modul eindeutig.
Ich bin mir aber noch nicht ganz sicher ob das alles so sinnvoll ist. 
Vorallem die unsägliche goto- Anweisung und das fixieren von Fehlercodes 
mittels define...
Was sagt ihr dazu?
Grüsse!
M.

von dotm (Gast)


Lesenswert?

sorry, ich meinte natürlich:
1
  freturn:
2
  // Muss vor return auf jeden Fall ausgeführt werden
3
  ReleaseMutex(funktion_eins_mutex);
4
  return error_status_message;

von Klaus W. (mfgkw)


Lesenswert?

Es ist faszinierend, was man alles unternimmt, um C++ und Ausnahmen zu 
vermeiden.
Wenn sonst nichts wirklich ernstes dagegen spricht, würde ich einfach zu 
C++ raten.

In C ist vernünftige Fehlerbehandlung wesentlich arbeitsaufwändiger, 
unleserlicher und entweder auch nicht umsonst oder sehr lückenhaft und 
vor allem fehleranfälliger.

: Bearbeitet durch User
von Alexander B. (leuchte)


Lesenswert?

Klaus Wachtler schrieb:
> Es ist faszinierend, was man alles unternimmt, um C++ und Ausnahmen zu
> vermeiden.

Komisch! Eine ganze Latte an aktuellen Projekten verwendet schlichtes C 
bei Mikrocontrollern. Ob die den technischen Fortschritt verpennt haben? 
Ich denke nicht!

Klaus Wachtler schrieb:
> In C ist vernünftige Fehlerbehandlung wesentlich arbeitsaufwändiger,
> unleserlicher und entweder auch nicht umsonst oder sehr lückenhaft und
> vor allem fehleranfälliger.

Tja, das ist mal wieder so eine Pro-/Contra-Sache. Dafür geht er eben 
den Nachteilen von C++ aus dem Weg, indem er eben jene Sprache 
vermeidet. ;-)

Um kurz das auf das Thema zurückzukommen: Meine Fehlerbehandlung ist 
ähnlich der der Win32 API bzw. der des TO. Überall passende Fehlercodes, 
ein einheitlicher Typ für eben jene und Unterstützung mit ein paar 
gescheiten Makros.

Im Grunde könnte man sagen, man hätte Exceptions in C nachgerüstet. Aber 
die Sache hat doch in meinen Augen einen ernormen Vorteil:

Die Fehlerbehandlung ist sichtbar!
-> Es fliegen keine unsichtbaren Exceptions durch die Gegend.
-> Es werden nicht implizit Destruktoren aufgerufen.
-> Der Programmfluss ist exakt so wie er im Code steht.

Dass eine durchdachte Fehlerbehandlung in C fehleranfälliger ist als in 
C++ wage ich stark zu bezweifeln.

von Tom (Gast)


Lesenswert?

dotm schrieb:
> Vorallem die unsägliche goto- Anweisung

Dein Fall ist einer der wenigen, bei dem goto sinnvoll ist. Die gotos 
teilweise hinter einem harmlos klingenden CHECKERROR() zu verstecken, 
ist hingegen böse und wird dich den Flüchen der Menschen aussetzen, die 
das irgendwann mal weiterbearbeiten müssen.

von dotm (Gast)


Lesenswert?

Somit ist eigentlich einer implementierung nach meinem Schema nichts 
mehr auszusetzen (ausser dass ich das CHECKERROR evt weglasse).
Irgendwelche Verbesserungsvorschläge?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

dotm schrieb:
> typedef enum
>   {
>   ERROR = 0,
>   SUCCESS = !ERROR
>   } ErrorStatus;

Ziemlicher Humbug.  Das erste Element einer Aufzählung in C hat
immer den Wert 0, wenn man nichts angibt, damit ist das "= 0"
völlig überflüssig.

!0 hat immer den Wert 1 in C, ist also genauso überflüssig …

Klaus Wachtler schrieb:
> Es ist faszinierend, was man alles unternimmt, um C++ und Ausnahmen zu
> vermeiden.

Man muss allerdings der Gerechtigkeit halber sagen, dass gerade
Exceptions (so schön bequem sie sein mögen) sich schnell zu
Codemonstern im generierten Code aufblähen können.  Meiner Erinnerung
nach sind sie aus ebendiesem Grund aus dem Pflichtumfang von Embedded
C++ weggelassen worden.

Wenn die Ressourcen da sind, würde ich sie allerdings einer händischen
Fehlerbehandlung in C vorziehen.

: Bearbeitet durch Moderator
von dotm (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Ziemlicher Humbug.  Das erste Element einer Aufzählung in C hat
> immer den Wert 0, wenn man nichts angibt, damit ist das "= 0"
> völlig überflüssig.

das ist nicht von mir sondern von ST.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

dotm schrieb:
> das ist nicht von mir sondern von ST.

Das ging auch nicht gegen dich, sondern gegen den, der das verzapft
hat.

Es wäre mir Grund genug, derartigen Code nicht als eigenes Vorbild
zu nehmen.  Wenn jemand derartige Verrenkungen fabriziert, um Dinge
zu erreichen, die vom C-Standard klipp und klar geregelt sind, dann
ist mir das ein Hinweis, dass derjenige von der Sprache nicht gerade
viel Ahnung hat.

von Konrad S. (maybee)


Lesenswert?

dotm schrieb:
> Irgendwelche Verbesserungsvorschläge?

Ich würde den Fehlerstatus mit einem Wert ungleich 0 initialisieren und 
erst unmittelbar vor dem Fehler-Label auf 0 setzen.

von Rolf Magnus (Gast)


Lesenswert?

Alexander B. schrieb:
> -> Es werden nicht implizit Destruktoren aufgerufen.

Die anderen Punkte kann ich nachvollziehen, aber diesen einen Punkt sehe 
ich als den größten Nachteil an. Daß die aufgerufen werden, ist doch 
grad die große Stärke von Exceptions in C++. Es werden ja auch nicht 
willkürlich irgendwelche Destruktoren aufgerufen, sondern die von den 
Objekten, deren Scope verlassen wird, die also eigentlich eh nicht mehr 
exisiteren. Deren Hinterlassenschaften müssen aufgeräumt werden, und 
C++ sorgt dafür, daß man das nicht vergessen kann.
Wenn ich beispielsweise in einer Funktion ein lokal angelegtes File 
einlese und nach dem Öffnen der Parser eine Exception wirft, wird das 
File ganz automatisch geschlossen, bevor aus der Funktion gesprungen 
wird.

Tom schrieb:
> dotm schrieb:
>> Vorallem die unsägliche goto- Anweisung

Warum sollte die "unsäglich" sein? Nur weil das gerne mal behauptet 
wird?

> Dein Fall ist einer der wenigen, bei dem goto sinnvoll ist. Die gotos
> teilweise hinter einem harmlos klingenden CHECKERROR() zu verstecken,
> ist hingegen böse

Das sehe ich ganz genauso. Mit goto kann man in dieser Situation den 
leserlichsten und einfachsten Code produzieren. Aber daß hinter einem 
Makro irgendwelche Sprünge im Programm verborgen sind, damit rechnet man 
doch nicht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.