Forum: Mikrocontroller und Digitale Elektronik Error Codes (Embedded Programming)


von Leo (Gast)


Lesenswert?

Hallo Community,

mein Frage bezieht sich auf Best Practice bezüglich der Rückgabe von 
Error Codes aus Funktionen in der Programmiersprache C (Embedded).

1. Möglichkeit

Man definiert in einem Header einen enum mit möglichen Error Codes, wie 
z.B.

mytype.h
1
typedef enum
2
{
3
  ERROR_CODE_FAILURE,
4
  ERROR_CODE_SUCCESS,
5
  ERROR_CODE_NULL_POINTER,
6
  ERROR_CODE_INDEX_OUT_OF_RANGE,
7
  ERROR_CODE_INVALID_PARAMETER,
8
  ...
9
}
10
error_codes_t;

Diese Header-Datei wird nun in den zu entwickelnden Modulen inkludiert 
und die entsprechenden Funktionen geben als Return-Value eben einen Wert 
von error_codes_t zurück.

Auf diese Art und Weise wird es auch in der STM32 SPL (Standard 
Periphery Library) gemacht. Dort wird in stm32f10x.h ein typedef enum 
ErrorStatus definiert und dieser in sämtlichen Modulen der SPL 
benutzt.


2. Möglichkeit

Jedes Modul definiert in seinem Header einen eigenen Typedef mit Error 
Codes, wie z.B.

Modul_A.h
1
typedef enum
2
{
3
  MODUL_A_FAILURE,
4
  MODUL_A_SUCCESS,
5
  ...
6
}
7
module_a_error_codes_t;

Modul_B.h
1
typedef enum
2
{
3
  MODUL_B_FAILURE,
4
  MODUL_B_SUCCESS,
5
  ...
6
}
7
module_b_error_codes_t;


Wie handhabt ihr Error Codes in C (Embedded) Projekten, bzw. welche der 
genannten zwei Möglichkeiten bevorzugt ihr.
Oder vielleicht habt ihr noch eine nicht genannte Möglichkeit?!

Vielen Dank schon einmal.
Gruß Leo

von ui (Gast)


Lesenswert?

Ich habe es bis jetzt hauptsächlich als eine Mischung von beiden 
gesehen. Und mach das auch selbst ausschließlich so.

Es gibt halt Modulspezifische Rückgabewerte und auch globale.

von Sebastian K. (sek)


Lesenswert?

Habe früher beides gemacht, bin mittlerweile jedoch vollständig bei 
Möglichkeit 1. angelangt und definiere Fehlercodes nur noch global.

Das hat zwar den Nachteil, dass man sämtliche Fehlercodes zentral und 
sauber hinterlegen muss. Jedoch den großen Vorteil, dass alle 
Fehlercodes in einer großen Übersicht verfügbar sind und 
Doppelbelegungen weitestgehend vermieden werden. So vermeide ich 
weitestgehend, dass ich bei der Fehleranalyse später feststelle, dass in 
zwei Modulen gleiche Fehlercodes verwendet wurden und die Aussagekraft 
bei Null liegt.

von Alfred (Gast)


Lesenswert?

ich mache das auch mit einem globalen enum. Allerdings
gruppiere ich:

enum
{
SUCCESS = 0,

WRN_xxx = 0x10000,
WRN_yyy,
WRN_zzz

ERR_xxx = 0x20000,
ERR_yyy,
ERR_zzz,

PANIC_xxx = 0x40000,
PANIC_yyy
};

Das Gruppieren auch wahlweise nach Modulen:

enum
{
DHCP_NO_CONNECT = 0x10000
DHCP_TIMEOUT,
DHCP_xxx

UDP_NO_CONNECT = 0x20000,
UDP_xxx
}

von Leo (Gast)


Lesenswert?

Hallo,

vielen Dank für die Antworten!
Ich habe noch ein Frage zu diesem Thema.

Wie handhabt ihr folgenden Fall:

Es existieren eine Reihe von Error Codes in einem gemeinsamen Enum...
1
typedef enum
2
{
3
  ERROR_CODE_FAILURE,
4
  ERROR_CODE_SUCCESS,
5
  ERROR_CODE_NULL_POINTER,
6
  ERROR_CODE_INDEX_OUT_OF_RANGE,
7
  ERROR_CODE_INVALID_PARAMETER,
8
  ...
9
}
10
error_codes_t;

Dazu existieren zwei Funktionen, welche eben diesn Enum als Rückgabe 
besitzen.

/* Function_A liefert aus dem Enum jedoch nur folgende Werte:
   ERROR_CODE_FAILURE,
   ERROR_CODE_SUCCESS,
 */
1
error_codes_t Function_A (void)
2
{
3
 ...
4
}

/* Function_B liefert aus dem Enum jedoch nur folgende Werte:
   ERROR_CODE_FAILURE,
   ERROR_CODE_SUCCESS,
   ERROR_CODE_INVALID_PARAMETER,
 */
1
error_codes_t Function_B (uin8_t new_value)
2
{
3
 ...
4
}

Sollte eine Funktion welche diesen Enum als Rückgabe benutzt, immer alle 
Enum Werte auch bedienen oder ist es in Ordnung, wenn die Funktion nur 
z.b nur 2 von 5 Werten aus dem Enum benutzt?

Danke schon einmal im Voraus!
Gruß Leo

von René H. (Gast)


Lesenswert?

Hallo Leo,

Leo schrieb:
> 2 von 5 Werten aus dem Enum benutzt?

das ist völlig ok und sogar der Normalfall.

Grüsse,
René

von Leo (Gast)


Lesenswert?

Hallo,

vielen Dank für die Antwort.

Jetzt gleich noch eine Fragen zu Error Codes.
Was ist eurer Meinung nach Best Practice für die Wertigkeit der Error 
Codes?

Sollte ERROR_CODE_FAILURE = 0 sein
oder
sollte ERROR_CODE_SUCCESS = 0 sein?

Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS = 
!ERROR.

Wenn man jedoch folgenden Enum verwendet...
1
typedef enum
2
{
3
  ERROR_CODE_SUCCESS = 0,
4
  ERROR_CODE_FAILURE,
5
  ERROR_CODE_NULL_POINTER,
6
  ERROR_CODE_INDEX_OUT_OF_RANGE,
7
  ERROR_CODE_INVALID_PARAMETER,
8
  ...
9
}
10
error_codes_t;

... kann man innerhalb einer Funktion auftretende Error zählen
1
void Do_Some_Stuff (void)
2
{
3
 uint8_t error_count = 0;
4
5
 error_count += Some_Function_A (void); //liefert error_codes_t
6
 error_count += Some_Function_B (void); //liefert error_codes_t
7
 ...
8
}

Welche Reihenfolge verwendet / bevorzugt ihr für Error Codes.
Danke schon einmal für eure Antworten!

Gruß Leo

von W.S. (Gast)


Lesenswert?

Leo schrieb:
> ERROR_CODE_FAILURE

Und so ein Bandwurm-Wort ist für dich aussagekräftig genug?
Mir wäre das zu albern: 18 Zeichen und es steht nur drin "Fehler" - 
jedoch nix konkreteres.

Im Grunde kannst du das ganze Wort knicken. Wenn in deinem Almanach was 
Aussagekräftigeres stände, wäre es besser. Also sowas wie errTimeout, 
errOutOfRange, errParity, errWrongID, errStuckLow und so weiter.

W.S.

von René H. (Gast)


Lesenswert?

Hallo Leo

Leo schrieb:
> Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS =
> !ERROR.

In der Regel wird für Failure 0 verwendet (false), !false ist true also 
Success. Einem Standard gibt es da aber nicht. Ich würde im Enum Success 
als 1 wählen.

Aber deine Intention verstehe ich, weil ja danach wieder Fehlercodes 
kommen.

Grüsse,
René

von Feldstecher (Gast)


Lesenswert?

René H. schrieb
> In der Regel wird für Failure 0 verwendet (false), !false ist true also
> Success.
Gerade andersrum, Fehler=0 heisst kein Fehler, und dann gibt es 
verschiedene Fehler-Arten (1,2,3...). In deinem Fall muesste es einen 
Fehler und mehrere Erfolgscodes geben, damit es sinnig ist.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Vielleicht sollte man da auch nicht zuviel Wissenschaft draus machen. 
Letztlich ist's wurscht, was schiefgelaufen ist, wenn was schiefgelaufen 
ist (Ausser vielleicht fuer Debugzwecke).
Aber ob in der Waschmaschine kurz vorm Schleudern ein NULL-Pointer 
auftritt oder irgendwas auf -EINVAL steht, wird Klementine wenig 
kratzen...

Gruss
WK

von A. S. (Gast)


Lesenswert?

Entweder bool: (success =1, Fehler 0)

Oder Codes, dann success = 0.

Niemals success=1 und 2 ist ein Fehler.

von Rolf M. (rmagnus)


Lesenswert?

Leo schrieb:
> Sollte eine Funktion welche diesen Enum als Rückgabe benutzt, immer alle
> Enum Werte auch bedienen oder ist es in Ordnung, wenn die Funktion nur
> z.b nur 2 von 5 Werten aus dem Enum benutzt?

Wenn dein Enum alle Fehlermöglichkeiten enthält, die es im ganzen 
Programm geben kann, ist es wenig sinnvoll, die bei jedem 
Funktionsaufruf allesamt abzuprüfen. Und was sollte denn auch die 
Reaktion auf einen Fehlercode sein, der gar nicht auftreten kann?
Es muss aber auf jeden Fall dokumentiert sein, welche Fehlercodes die 
Funktion zurückliefern kann und für jeden Fehlercode die exakte 
Situation, in der er im Kontext dieser Funktion auftritt.

Leo schrieb:
> ... kann man innerhalb einer Funktion auftretende Error zählen

Braucht man das denn? In der Regel bricht doch eine Funktion beim ersten 
Fehler ab, weil sie danach eh nichts sinnvolles mehr machen kann. Und 
selbst wenn sie weitermachen kann: Was hab ich davon, zu wissen, dass 
jetzt genau 3 Fehler aufgetreten sind, wenn ich nicht weiß, welche?

Leo schrieb:
> Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS =
> !ERROR.

Diese Definition über die Negierung finde ich ziemlich blöd. Meisten 
wird sie von Leuten verwendet, die ein paar grundlegende Dinge nicht 
verstanden haben. In der Regel steht da die falsche Annahme dahinter, 
dass !0 nicht das selbe sei wie 1.

Dergute W. schrieb:
> Vielleicht sollte man da auch nicht zuviel Wissenschaft draus machen.
> Letztlich ist's wurscht, was schiefgelaufen ist, wenn was schiefgelaufen
> ist (Ausser vielleicht fuer Debugzwecke).

Das heißt, dass du grundsätzlich nur auf "Fehler" oder "nicht Fehler" 
prüfst und es dir immer egal ist, was für ein Fehler aufgetreten ist?

> Aber ob in der Waschmaschine kurz vorm Schleudern ein NULL-Pointer
> auftritt oder irgendwas auf -EINVAL steht, wird Klementine wenig
> kratzen...

Der Klementine nicht, aber die Waschmaschine will vielleicht 
unterschiedlich darauf reagieren, damit die Trommel beim schleudern der 
Klementine nicht um die Ohren fliegt.

Achim S. schrieb:
> Entweder bool: (success =1, Fehler 0)

Dann aber eher true/false.

> Oder Codes, dann success = 0.
>
> Niemals success=1 und 2 ist ein Fehler.

Würde ich auch so machen, wobei es an sich keine Rolle spielt, wenn man 
den Fehlercodes eh Namen gibt.

von J. F. (Firma: Père Lachaise) (rect)


Lesenswert?

Leo schrieb:
> typedef enum
> {
>   ERROR_CODE_SUCCESS = 0,
>   ERROR_CODE_FAILURE,
>   ERROR_CODE_NULL_POINTER,
>   ERROR_CODE_INDEX_OUT_OF_RANGE,
>   ERROR_CODE_INVALID_PARAMETER,
>   ...
> }
> error_codes_t;

Noch eine kleine Anmerkung die allerdings eher was mit der Formatierung 
zu tun hat: ich spendiere dem enum meist noch einen Namen also keine 
anonyme Deklaration:
1
typedef enum _error_code
2
{
3
...

Dazu noch den Singular anstelle des Plurals und keine ungarische 
Notation:
1
  ...
2
} error_code;

Aber das sind Details die auch eher vom Geschmack abhängen.

: Bearbeitet durch User
von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

J. F. schrieb:
> Leo schrieb:
>> typedef enum
>> {
>>   ERROR_CODE_SUCCESS = 0,
>>   ERROR_CODE_FAILURE,
>>   ERROR_CODE_NULL_POINTER,
>>   ERROR_CODE_INDEX_OUT_OF_RANGE,
>>   ERROR_CODE_INVALID_PARAMETER,
>>   ...
>> }
>> error_codes_t;
>
> Noch eine kleine Anmerkung die allerdings eher was mit der Formatierung
> zu tun hat: ich spendiere dem enum meist noch einen Namen also keine
> anonyme Deklaration:
>
>
1
> typedef enum _error_code
2
> {
3
> ...
4
>

Ja, aber ... Ein führender "_" bedeutet du bist im reservierten 
Namensraum von Bezeichnern. Gerade bei so generischen Bezeichnungen wie 
"_error_code" würde ich das vermeiden. Wer weiß was irgendwo intern 
gemacht wird.

Auch wenn dir bis heute nichts passiert ist:

1000 Mal berührt, 1000 Mal ist nichts passiert, 1000 und eine Nacht
und es hat Zoom gemacht.

Daher lieber zur Risikolimitierung und weil es kein besonderer Aufwand 
ist folgendes:
1
typedef enum error_code_

> Dazu noch den Singular anstelle des Plurals

Ja.

> und keine ungarische
> Notation:

Ja, aber "_t" sollte man aus anderen Gründen weglassen:

Die gute Nachricht zuerst, ein abschließendes "_t" ist keine ungarische 
Notation. Der Scheißdreck, der sich ungarische Notation nennt, sieht 
anders aus. Man könnte das "_t" durchgehen lassen, aber das 
abschließende "_t" ist reserviert!

> Aber das sind Details die auch eher vom Geschmack abhängen.

Nicht alles ist Geschmack. Das sind zum Teil Details, die Compiler- und 
Bibliotheks-Interna betreffen.

Der GCC hat eine Liste: 
https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html

von J. F. (Firma: Père Lachaise) (rect)


Lesenswert?

Hannes J. schrieb:
> Ja, aber ... Ein führender "_" bedeutet du bist im reservierten
> Namensraum von Bezeichnern. Gerade bei so generischen Bezeichnungen wie
> "_error_code" würde ich das vermeiden. Wer weiß was irgendwo intern
> gemacht wird.

Ein guter Punkt. Wie du siehst hat es bei mir noch zu Konflikten 
geführt, danke für den Hinweis.

> Der GCC hat eine Liste:
> https://www.gnu.org/software/libc/manual/html_node...

Auch danke dafür. Ein paar Sachen sieht man immer mal wieder, allerdings 
nicht zusammengefasst.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Leo schrieb:
> Der Enum ErrorStatus in stm32f10x.h definiert ERROR = 0 und SUCCESS =
> !ERROR.

 Und es sollte (meiner Meinung nach) gerade umgekehrt sein, also
 SUCCESS = 0 und ERROR = !SUCCESS.

 Warum ?

 Weil jeder Fehler normalerweise einen bestimmten bit setzt oder einen
 bestimmten Wert zurückliefert - keine bits gesetzt, kein Fehler.

 Mit enum kann man aber meistens nur einen Fehler zurückgeben, im
 Gegensatz dazu sind bei bits mehrere Fehlercodes möglich.
 Deswegen sollte man sich auch überlegen, ob enum mit bestimmten Werten
 anstatt mit bitpositionen immer die bessere Wahl ist.

 Sicher, bei 90% ist das der Fall, aber es gibt auch Situationen wo
 man nur bestimmte Parameter prüft, markiert diese als ERR oder OK und
 erst am Ende zurückkehrt und nicht sofort nach dem ersten Fehler 
abbricht.

von Peter D. (peda)


Lesenswert?

Es hat sich eingebürgert, daß 0 für Success steht. Z.B. das main gibt 
bei fehlerfreier Ausführung den Exitcode 0 zurück an das OS.

Es gibt aber auch Ausnahmen. Z.B. getchar gibt den Zeichencode (0..255) 
zurück bei Erfolg und einen negativen Wert bei Fehler (EOF = -1).

von Max G. (l0wside) Benutzerseite


Lesenswert?

WIMRE war das irgendeine Uneinigkeit zwischen BSD und anderen Unixen, 
die einen machten ERROR = 0, die anderen SUCCESS = 0.

Es gibt für beide Varianten gute Gründe. Wichtig ist eigentlich nur, 
nach der Entscheidung für eine das konsequent durchzuziehen. Am besten 
mit Zugriff nur über Makros (z.B. SUCCEEDED), dann kannst du die 
Implementierung abstrahieren.

von Leo (Gast)


Lesenswert?

Hallo,

vielen Dank für die vielen Antworten und Anregungen.

[c]
typedef enum
{
  ERROR_CODE_SUCCESS = 0,
  ERROR_CODE_FAILURE,
  ERROR_CODE_INVALID_PARAMETER,
  ...
}
error_code_td;

Ich tendiere auch dazu SUCCESS = 0 zu definieren und dann aufsteigend 
alle anderen Error Codes.

@Rolf Magnus
Ja, wenn man "globale" Error Codes verwendet muss jede Funktion 
dokumentieren, welche Error Codes sie liefert. z.B

/* @brief ...
 * @param none
 * @return ERROR_CODE_FAILURE ...
 * @return ERROR_CODE_SUCCESS ...
 */
error_code_td Do_Some_Stuff (void)
{
  ...
}

Rolf M. schrieb:
> Braucht man das denn? In der Regel bricht doch eine Funktion beim ersten
> Fehler ab, weil sie danach eh nichts sinnvolles mehr machen kann. Und
> selbst wenn sie weitermachen kann: Was hab ich davon, zu wissen, dass
> jetzt genau 3 Fehler aufgetreten sind, wenn ich nicht weiß, welche?

Es geht vielmehr weniger um das Zählen von Fehlern, eher der Ermittlung 
ob innerhalb einer Funktion ein Fehler aufgetreten ist.
MISRA-C erlaubt z.B. nur ein return-Statement und so kann man am Ende 
einer Funktion ermitteln, ob ein Fehler aufgetreten ist.

J. F. schrieb:
> Noch eine kleine Anmerkung die allerdings eher was mit der Formatierung
> zu tun hat: ich spendiere dem enum meist noch einen Namen also keine
> anonyme Deklaration:
> typedef enum _error_code
> {
> ...

Welchen Vorteil hat die Vergabe eines Namen für den Enum, wenn man 
diesen eh mit typedef definiert?

https://www.gnu.org/software/libc/manual/html_node/
https://www.gnu.org/software/libc/manual/html_node/Error-Reporting.html#Error-Reporting

Das Error-Reporting wie es GCC nutzt, sehe ich für den Gebrauch bei 
Embedded Programming ohne OS, RTOS, usw. weniger geeignet.

Ich suche eher nach der Best Practice für Error Handling auf unterster 
Ebene (nur in Verbindung mit Libs wie, STM32 SPL / HAL oder ähnliche.
Ich würde gerne ein allgemeines Error Handling "Interface" für alle 
meine eigens entwickelten C-Module entwickeln.

Benutzt ihr nun globale Error Codes in euren Projekten / Bibliotheken?

von S. R. (svenska)


Lesenswert?

Leo schrieb:
> Benutzt ihr nun globale Error Codes in euren Projekten / Bibliotheken?

Nein, allerdings hantiere ich nur mit eher kleinen Programmen und da 
reicht ein Ad-Hoc-Ansatz aus. Allerdings kein Industriemaßstab.

Meine Treiber geben meist nur {0 = Success, 1 = Fehler} zurück, genauere 
Angaben brauche ich nur selten, weil die Reaktion darauf meist eine 
sofortige Endlosschleife oder ein Reset ist.

Bringt ja nichts, tausende Fehlerquellen zu definieren, die am Ende 
ohnehin alle die selbe Reaktion hervorrufen.

von Rolf M. (rmagnus)


Lesenswert?

Hannes J. schrieb:
> Die gute Nachricht zuerst, ein abschließendes "_t" ist keine ungarische
> Notation. Der Scheißdreck, der sich ungarische Notation nennt, sieht
> anders aus.

Der Scheißdreck, der von manchen (vor alle Microsoft) für ungarische 
Notation missverstanden wird, meinst du wohl eher. Den Datentyp in den 
Namen zu schreiben, ist eigentlich nicht der Sinn der ungarischen 
Notation.

von A. S. (Gast)


Lesenswert?

Die grundsätzliche Frage ist doch, ob und wer was mit den Error-codes 
anfangen soll. Dazu sind auch verschiedene Fehler-*Qualitäten* zu 
unterscheiden:
 - kritisch / unkritisch
 - im Normallfall möglich / nur bei Programmierfehlern möglich

Wenn jeder Rückgabewert abgefragt werden muss, dann ist keine sinnvolle 
Applikation mehr möglich. Aus diesem Grunde ist es notwendig, die 
allermeisten Funktionen so auszulegen, dass Rückgabewerte nicht 
ausgewertet werden müssen, sondern, dass
 - dies in einer Art Log protokolliert wird
 - ein gefahrloser Weiterbetrieb ermöglicht wird, falls eben möglich.

Wenn ich unkonditional "InitPort(X1, PortModeUART);" schreibe, und X1 
kann kein UART sein, dann nützt mir ein Rückgabewert nichts. Ich muss 
dafür sorgen, dass es im Error-Log erkannt wird UND dass "SendePort(X1, 
"Hallo Welt");" nicht zum unkontrollierten Absturz führt.

Der einfachste "Log" ist dabei ein Fehler-Flag, dass ab dem ersten 
Fehler 1 bleibt. Das kann dann auf beliebige Bitfelder, Fehlerspeicher 
oder Log-Einträge mit _FILE__ und __LINE_ erweitert werden. Und auf 
Callbacks oder geregeltem Shutdown für kritische Fehler.

Wenn ich keinen Benutzer habe (also oft im embedded Bereich) reicht 
meist der boolsche Rückgabewert: OK oder !OK. Die "Fehler", die im 
Normalbetrieb vorkommen können, (z.B. Sensor defekt) sind eh keine 
Fehler, sondern "normale Betriebszustände", die auch normal gehandhabt 
werden müssen.

Beispiel:
1
errcode GetValue(Port X, int *Value);

Das ist meistens Quatsch. Man kommt schnell dazu, dass Value kein Skalar 
sein sollte, sondern ein Objekt mit Wert und Status. Und dass Getvalue 
dieses Objekt statt Fehlercode zurückliefern sollte, also:
1
tValue GetValue(Port X);

Der Applikationscode wird damit klarer. Und wenn X nicht initialisiert 
ist oder out of range oder Kabelbruch hat, dann sollte mindestens ein 
"Valid-Bit" in tValue auf 0 stehen. Und die nachfolgenden Routinen (z.B. 
zur Anzeige) sollten tValue nativ verarbeiten. Das ist etwas fundamental 
anderes wie parallel "Error-Codes" zu berücksichtigen.

von S. R. (svenska)


Lesenswert?

Achim S. schrieb:
> Wenn ich keinen Benutzer habe (also oft im embedded Bereich) reicht
> meist der boolsche Rückgabewert: OK oder !OK.

Das ist einer der wenigen wirklich harten Nachteile von C: Es gibt nur 
einen Rückgabewert. In Assembler kann man einfach ein Flag 
(üblicherweise Carry) dafür "missbrauchen".

Aber wie gesagt, eine wirkliche Lösung habe ich nicht.

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> Das ist einer der wenigen wirklich harten Nachteile von C: Es gibt nur
> einen Rückgabewert.

Der aber auch ein struct sein kann, womit dieser Nachteil nicht wirklich 
existiert.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
>> Das ist einer der wenigen wirklich harten Nachteile von C:
>> Es gibt nur einen Rückgabewert.
>
> Der aber auch ein struct sein kann, womit dieser Nachteil nicht wirklich
> existiert.

Eine struct zurückgeben ist aufwändiger, als einen einzelnen Wert 
zurückzugeben, und wird nicht von jedem Compiler unterstützt (der SDCC 
kann es z.B. nicht).

von Erfahrener Programmierer (Gast)


Lesenswert?

Best Pactice? Million Fliegen können nicht irren, Sch*sse ist gut?

Ganz ehrlich? Modulübergreifende, globale Error-Codes sind Grütze!

Warum? Sie erschweren die Wiederverwendung da sie alle Module über den 
Fehlertyp koppeln. Auch ist anhand des Interface nicht ersichtlich 
welche Fehler eine Funktion erzeugen kann.

  ERROR_SUCCESS ???

Semantischer Nonsense! Sein oder Nicht-Sein? Fehler oder Erfolg?

  ERROR_CODE_NULL_POINTER,
  ERROR_CODE_INVALID_PARAMETER,

Wann wird der Null-Pointer zum ungültigen Parameter?

Noch mehr Semantik: Wer will eine Funktion aufrufen, die immer einen 
Fehlercode zurück gibt?

Eine Funktion/Methode/Prozedur etc. sollte dem Aufrufer besser sagen ob 
sie erfolgreich war oder nicht! Das nennt sich "Status". Dieser Status 
kennt nur zwei Werte: "Alles gut", oder "Hat nicht geklappt". (Das passt 
übrigens sehr gut in ein 'bool'...)

Erst wenn etwas nicht geklappt hat, gibt es (möglicherweise) eine 
genauere Beschreibung für das Problem! Die semantische Hierarchie sieht 
so aus:

 --+-- Alles Ok
   |
   +-- Fehler --+-- Kein Speicher frei
                |
                +-- Datei nicht gefunden
                |
                +-- [...]

Selbstverständlich hat jede Funktion/Modul seinen eigenen Fehlertyp!

von Nop (Gast)


Lesenswert?

S. R. schrieb:

> Eine struct zurückgeben ist aufwändiger, als einen einzelnen Wert
> zurückzugeben

Das stimmt. Für den Fall kann man aber auch das return-struct by 
reference übergeben und ausfüllen. Das ist zwar syntaktisch kein 
return-Wert, ist aber ein semantisch übliches C-Idiom. Erkennt man auch 
daran, daß im Prototypen der Funktion kein const steht (sofern der 
Programmierer da halbwegs sauber gearbeitet hat).

Das ist eigentlich die Art, wie ich gerne komplexere Sachen zurückgebe, 
zumal man so einen Zeiger auch ohne großen Kopieroverhead über mehrere 
Funktionsebenen durchreichen kann.

> und wird nicht von jedem Compiler unterstützt (der SDCC kann es z.B. nicht).

Das ist ja nun kein Nachteil von C, sondern vom Compiler.

von S. R. (svenska)


Lesenswert?

Nop schrieb:
> Für den Fall kann man aber auch das return-struct by
> reference übergeben und ausfüllen.

Dann kann man auch gleich den Fehlercode zurückgeben und 
by-reference-Parameter nutzen. Macht (in meinen Augen) den Code besser 
lesbar.

Nop schrieb:
>> und wird nicht von jedem Compiler unterstützt
>> (der SDCC kann es z.B. nicht).
> Das ist ja nun kein Nachteil von C, sondern vom Compiler.

Eher der Methode, denn sie funktioniert nicht mit jedem Compiler (und 
offiziell erst seit 28 Jahren). ;-)

von J. F. (Firma: Père Lachaise) (rect)


Lesenswert?

Leo schrieb:
> Welchen Vorteil hat die Vergabe eines Namen für den Enum, wenn man
> diesen eh mit typedef definiert?

Durch dieses Vorgehen kann man bspw. unter Eclipse im "Outline" Fenster 
die verwendeten Enumerationen samt Inhalten sehen, die sonst alle gleich 
mit (anonymous) geführt würden.

von Vincent H. (vinci)


Lesenswert?

Ich persönlich bin eigentlich schon ein Freund globaler Error-Codes. 
Meist nutz ich auch einfach das, was C/C++ ohnehin in der Umgebung 
definiert. Grad was klassische Peripherie angeht teilt sich ein Großteil 
sowieso die gängisten Möglichkeiten.

Ein "Protocol Error" macht unter SPI und I2C Sinn, ein "Timeout" kanns 
für so gut wie alles geben, etc.
Mehr als den Funktionsaufruf zu wiederholen oder schlichtweg nur den 
Fehler irgendwohin zu leiten ist bei vielen embedded Systemen eh nicht 
drin.


Tuts ein klassischer "std::errc" nicht, dann neige ich eher dazu die 
Unix-typische Herangehensweise zu nutzen. Sprich Integer Rückgabewert 
und alles was nicht Null ist (und meist <0) tut weh.


/edit
Als C++ Freund ist meine tatsächliche Implementierung natürlich ein 
optional<errc>.

: Bearbeitet durch User
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.