Errorcodes

Vor Jahren habe ich Exception in C++ als “die beste und genaueste” Methode beschrieben, um Fehler zu kommunizieren.

Doch im C basierten GATE Projekt steht dieses Feature auf unterster Ebene nicht zur Verfügung.

Also tun wir das, was seit den Anfängen der Programmierung getan wurde:
Wir ziehen Zahlen heran um anzuzeigen, was uns gerade nicht gepasst hat.


Die einfachste Methode rückzumelden, ob eine Funktion erfolgreich war oder nicht, ist 1 oder 0, also true und false.

Integer-Return-Values sind für die Umsetzung auf CPU Instruktionen recht effizient und vermutlich hat sich deshalb diese Art der “Fehler-Kommunikation” in C recht stark etabliert.

Will man jetzt aber das Warum wissen, darf man sich in die Dokumentation einwühlen. Grundsätzlich kenne ich folgende Ansätze:

  1. Der Return-Value gibt über den Erfolg und Misserfolg Bescheid, eine zweite Funktion sagt genaueres.
  2. Der Return-Value ist ein Status-Code, sein Wert beinhaltet alles: Erfolg, Misserfolg und Begründung.
  3. Eine Funktion löst je nach Erfolg oder Misserfolg unterschiedliche Callbacks aus.

Der erste Fall ist in C und POSIX weit verbreitet. Die meisten Funktionen haben einen int Return-Value und melden mit 1 (oder ungleich NULL) einen Erfolg, und mit NULL einen Misserfolg. Ein abgewandelte Form davon ist, wenn der Return-Value ein Pointer ist, dann ist ein NULL-Pointer der Misserfolg und alles andere ein Erfolg, wobei der Return-Value dann gleich als Objekt weiter benutzt wird.

Zusätzlich braucht man dann noch eine zweite Funktion wie errno, die einen konkreteren Fehlercode zurückgibt und uns so sagt, was genau schief gelaufen ist.

Hier fingen früher die Probleme an, denn diese Fehlercode wurde in eine globale Variable geschrieben und auch von dort wieder ausgelesen. Als der Umstieg auf Threads kam, musste die globale Variable durch eine Thread-lokale Variable ersetzt werden, um weder die API noch die Konsistenz zu brechen.

Windows nutzt mit seinem GetLastError() das gleiche Konzept für viele seiner C APIs.

Der zweite Ansatz gefällt mir allerdings besser. Auch hier werden int Werte zurückgegeben, die aber gleich die Fehlerinformation wiedergeben, falls es sich um einen Misserfolg handelt. Hier ist die 0 der Indikator für “Kein Fehler”. Alles andere sind konkrete Fehlercodes.

Dieses Schema ist ebenfalls weit verbreitet und wird in Microsoft’s COM benutzt. (Schon witzig, dass Microsoft sich hier selbst mit zwei Ansätzen Konkurrenz macht.)

Diese Fehlercodes sind aus meiner Sicht eher mit Exceptions vergleichbar, weil es immer nur einen korrekten Abschluss einer Funktion gibt (Code 0), und alles andere ein “Ausnahmefall” ist.

Der dritte Ansatz kommt in erster Linie in der asynchronen Programmierung vor, wo das Starten einer Funktion gar kein Ergebnis liefert, sondern erst später auf den Ausgang reagiert wird. In Javascript trifft man es recht häufig an, aber auch im dotNet Framework wird durch Delegates etwas ähnliches umgesetzt.


Im GATE Projekt wird in der Regel Ansatz 2 angewendet und interne Fehler werden auf halbwegs generische Fehlercodes abgebildet. Eine Ausnahme bilden Allokationen. Hier werden Pointer zurückgegeben, weil im Falle eines Null-Pointers die einzige Begründung “Kein freier Speicher” möglich ist.

Der C++ Layer wandelt dann die Error-Codes in Exceptions um, die eine Textbeschreibung und den Funktionsnamen tragen.

Die aus meiner Sicht größte Vorteil von Exception entsteht für das Debugging, weil die Exception auch noch die Ursprungsinformation in Form des Funktionsnamens in sich tragen kann. Ein Fehlercode alleine weiß ja nicht, auf welcher Ebene er ausgelöst wurde.

Natürlich wäre es auch möglich gewesen in C eine Exception-Struktur nachzubilden. Das hätte aber derart viel Overhead im Code produziert, dass es einfach nicht mehr tragbar ist.

Wer jetzt übrigens glaubt, dass Exceptions viel ineffizienter sind als Return-Values, der irrt (manchmal). Moderne Compiler generieren Exception Code äußerst effizient und können vor allem im Nicht-Fehlerfall schneller arbeiten, als C Code, der stets einen Return-Value generieren muss.