Forum: Compiler & IDEs Datenyp Rückgabewert von Funktionen


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich bin mal wieder bei einer Null-Frage (d.h. eine Frage, die ich schon 
vor langer, langer Zeit hätte klären sollen).

Sehr viele Funktionen in C geben einfach nur einen Fehlercode zurück. 
Traditionell haben diese Funktionen den Datentyp "int" und Fehlercodes 
("EXIT_SUCCESS" u.Ä.) sind als "defines" in einem Header (stdlib.h) 
hinterlegt.

Auf dem AVR bin ich mir sehr sicher, niemals mehr als 8 Bit für einen 
Fehlercode zu benötigen, also ist der Rückgabewert für diese Funktionen 
bei mir plattformübergreifend "int_fast8_t".

Andererseits könnte ich mir auch ein enum mit den möglichen Fehlercodes 
definieren, mit dem Flag "-fshort-enums" kompilieren und daraus einen 
Datentyp "return_t" definieren.

Oder.

Welchen Datentyp nutzt man heutzutage für die Rückgabe von Fehlercodes?

Viele Grüße
W.T.

von Karl H. (kbuchegg)


Lesenswert?

Walter T. schrieb:

> Sehr viele Funktionen in C geben einfach nur einen Fehlercode zurück.
> Traditionell haben diese Funktionen den Datentyp "int"

Das liegt in der Geschichte.

int war von Anfang an in C als der 'Leib- und Magen' Datentyp für eine 
bstimmte Maschine gedacht. D.h. der Datentyp mit dem eine CPU 
normalerweise am schnellsten und einfachsten hantieren kann (und der 
noch über einen genügend grossen Zahlenraum verfügt, so dass man auch 
etwas damit machen kann).

Das traf auf viele Workstations und größere Rechner zu. Nur gab es zur 
damaligen Zeit noch keine Mikrocontroller bzw. die 8 Bit Szene lag mehr 
oder weniger in den Kinderschuhen. Niemand konnte sich damals 
vorstellen, dass es möglich wäre einen kompletten Computer in weniger 
als 2 Kubikmeter Gehäuse unterzubringen.

> Welchen Datentyp nutzt man heutzutage für die Rückgabe von Fehlercodes?

Auf einem AVR würde ich zu uint8_t greifen. So wie für fast alles, was 
im Wertebereich nicht explizit einen 16 Bit int erfordert.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Karl H. schrieb:
> Auf einem AVR würde ich zu uint8_t greifen. So wie für fast alles, was
> im Wertebereich nicht explizit einen 16 Bit int erfordert.

Hallo Karl-Heinz,
danke für Deine Ausführungen. Das heißt für mich: Wenn ich ein wenig 
plattformunabhängiger bleiben will, ist uint_fast8_t der richtige 
Datentyp. Vielleicht sollte ich mir einfach ein kurz-typedef ausdenken - 
der Typname ist so lang, daß es einfach irgendwie falsch aussieht...

Hat es einen bestimmten Grund, daß Du uint8_t und nicht int8_t nutzt? 
VerODERst Du einzelne Fehlerzustände im Rückgabewert?

von Amateur (Gast)


Lesenswert?

Wie Karl Heinz gesagt hat, wurde ursprünglich der Wert, der der 
natürlichen Datenbreite des jeweiligen Prozessors entspricht, als 
Standartrückgabewert definiert.

Dieses Konzept ist aber m.E. nicht mehr unbedingt zwingend.

Die heutigen Compiler machen daraus ein: "Ein Wert wird zurückgegeben".
Je nach Implementation bedeutet dies, dass Du ein Word ein Longword oder 
einen Zeiger (meist mehr als 8 Bit) zurückgeben kannst. Der Compiler 
verteilt dies auf mehrere Register und setzt sie bei der Rückgabe auch 
wieder zusammen.

"Ein Wert wird zurückgegeben" bedeutet in diesem Zusammenhang:
Es können z.B. 8 Bit char, unsigned char, word oder auch Pointer (oft 4 
Bytes) zurückgeliefert werden auch der Buchstabe 'A' (char) sowohl in 
der 8-Bit Version als auch in der mit 16-Bit (wide char), aber 2 Zeichen 
wie 'A' + 'B' oder "AB" gehen nicht. Auch wenn sie in zwei Bytes oder 
bei einem 16 Bit Prozessor in ein Register passen würden.

von (prx) A. K. (prx)


Lesenswert?

Walter T. schrieb:
> Hat es einen bestimmten Grund, daß Du uint8_t und nicht int8_t nutzt?

Ein Returnwert kleiner als int wird ab und zu vom Compiler zu int 
erweitert. Das ist bei uint8_t oft billiger als bei int8_t.

von Joe F. (easylife)


Lesenswert?

Da der Return-Wert eh nur temporär auf dem Stack landet, "kostet" er ja 
nicht wirklich etwas (Speicherbedarf).

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

umsomehr, als er üblicherweise nicht mal auf dem Stack zurückgegeben 
wird, sondern in einem Register (solange es ein einfacher Typ wie int 
oder Zeiger ist und keine struct).

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Walter T. schrieb:
> Das heißt für mich: Wenn ich ein wenig
> plattformunabhängiger bleiben will, ist uint_fast8_t der richtige
> Datentyp.

Ja, genau diesen Datentyp benutze ich in µC-Familien-übergreifender 
Software am liebsten, wenn der Wertebereich 0...255 vollkommen 
ausreicht.

von Ano (Gast)


Lesenswert?

Walter T. schrieb:
> Karl H. schrieb:
>> Auf einem AVR würde ich zu uint8_t greifen. So wie für fast alles, was
>> im Wertebereich nicht explizit einen 16 Bit int erfordert.
>
> Hallo Karl-Heinz,
> danke für Deine Ausführungen. Das heißt für mich: Wenn ich ein wenig
> plattformunabhängiger bleiben will, ist uint_fast8_t der richtige
> Datentyp. Vielleicht sollte ich mir einfach ein kurz-typedef ausdenken -
> der Typname ist so lang, daß es einfach irgendwie falsch aussieht...

Das typedef kann ich nur unterstützen. Einerseits kannst du dann 
einfacher den Datentyp wechseln wenn uint8_t (oder was auch immer) doch 
nicht ausreicht, andererseits ist ein Typ "error_code" quasi 
selbstsprechend.

von Sheeva P. (sheevaplug)


Angehängte Dateien:

Lesenswert?

Walter T. schrieb:
> ich bin mal wieder bei einer Null-Frage (d.h. eine Frage, die ich schon
> vor langer, langer Zeit hätte klären sollen).
>
> Sehr viele Funktionen in C geben einfach nur einen Fehlercode zurück.
> Traditionell haben diese Funktionen den Datentyp "int" und Fehlercodes
> ("EXIT_SUCCESS" u.Ä.) sind als "defines" in einem Header (stdlib.h)
> hinterlegt.
>
> Auf dem AVR bin ich mir sehr sicher, niemals mehr als 8 Bit für einen
> Fehlercode zu benötigen, also ist der Rückgabewert für diese Funktionen
> bei mir plattformübergreifend "int_fast8_t".
>
> Andererseits könnte ich mir auch ein enum mit den möglichen Fehlercodes
> definieren, mit dem Flag "-fshort-enums" kompilieren und daraus einen
> Datentyp "return_t" definieren.
>
> Oder.
>
> Welchen Datentyp nutzt man heutzutage für die Rückgabe von Fehlercodes?

Da kann man IMHO nur mit Radio Eriwan antworten: "kommt darauf an".

Worauf kommt es an? Darauf, was mit dem Fehlercode / Rückgabewert 
gemacht werden soll. Ich hab' da mal was vorbereitet (in C99), siehe 
Anhang.

Für die benötigte Funktion im Anhang reicht im Prinzip natürlich das 
aus, was divide0() macht: bei einer ZeroDivision gibt die Funktion den 
Wert 0.0 zurück, was im Kontext unmöglich ist und daher einen Fehler 
signalisiert. Sowas kann man machen, wenn man Fehler direkt nach dem 
Funktionsaufruf behandelt, sonst kann das sehr schnell häßlich werden.

Was divide1() und divide2() machen, ist IMHO fast noch fieser; divide1() 
hat nichtmal einen sprechenden Bezeichner für den Fehler, divide2() hat 
zwar einen Bezeichner, aber beide Funktionen benötigen jeweils eigene 
und weitgehend funktionsspezifische Datenstrukturen für die Rückgabe, 
auf deren Elemente dann zugegriffen werden muß. Möglich, aber gruselig, 
und relativ aufwändig zu pflegen und erweitern!

Die eleganteste, les-, auswert- und erweiterbarste Lösung ist IMHO die 
in divide3(): da wird eine Enum-Variable mit dem passenden Fehlercode 
gesetzt und kann an beliebiger Stelle ausgewertet werden. Die 
Enumeration kann da nicht nur den Fehlercode für eine, sondern auch die 
für andere Funktionen enthalten; die Fehlercodes können (bei 
entsprechender Deklaration) sogar verODERt werden. Und wenn eine 
Funktion nicht nur wie die hier auf einen einzigen Fehlercode 
ZeroDivision, sondern sehr viele verschiedene Fehler reagieren können 
soll, scheint mir die Auswertung mit switch() sehr viel flexibler und 
eleganter als alle anderen Möglichkeiten.

Insofern gibt es IMHO keinen Königsweg. Ich persönlich neige zu Lösungen 
wie in divide3(), finde globale Variablen aber unelegant und neige daher 
meistens dazu, die errno-Enum static in eine eigene Header-Datei zu 
packen und nur über definierte Funktionen darauf zuzugreifen, und dann 
kann man natürlich auch gleich etwas wie strerror(3) mit den einzelnen 
Meldungen implementieren. Das ist dann die hübscheste Lösung für alles.

von Rolf M. (rmagnus)


Lesenswert?

Sheeva P. schrieb:
> Für die benötigte Funktion im Anhang reicht im Prinzip natürlich das
> aus, was divide0() macht: bei einer ZeroDivision gibt die Funktion den
> Wert 0.0 zurück, was im Kontext unmöglich ist und daher einen Fehler
> signalisiert.

Warum sollte das unmöglich sein? 0.0/5.0 ist doch 0.0, oder nicht? Du 
könntest besser INFINITY oder NAN aus <math.h> zurückliefern.

von Sheeva P. (sheevaplug)


Lesenswert?

Rolf M. schrieb:
> Sheeva P. schrieb:
>> Für die benötigte Funktion im Anhang reicht im Prinzip natürlich das
>> aus, was divide0() macht: bei einer ZeroDivision gibt die Funktion den
>> Wert 0.0 zurück, was im Kontext unmöglich ist und daher einen Fehler
>> signalisiert.
>
> Warum sollte das unmöglich sein? 0.0/5.0 ist doch 0.0, oder nicht? Du
> könntest besser INFINITY oder NAN aus <math.h> zurückliefern.

Klar. Aber darum ging es ja nicht. <:)

von Cyblord -. (cyblord)


Lesenswert?

Rolf M. schrieb:
> Warum sollte das unmöglich sein? 0.0/5.0 ist doch 0.0, oder nicht?

Das hat aber jemand die Division durch Null verstanden....

> Du
> könntest besser INFINITY oder NAN aus <math.h> zurückliefern.
DAS wiederrum ist korrekt.

: 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.