Hallo Community,
ein Kommilitone sagte mir das ich unter keinen Umständen eine
Enumeration als Rückgabewert verwenden soll und schon garnicht bei Code
für embedded systeme.
Beispiel:
1
typedef enum {
2
ROT = 1,
3
BLAU,
4
GELB,
5
ORANGE
6
} FARBE;
7
8
FARBE exampleFnct (void) {
9
10
return ROT;
11
}
Was genau ist daran so falsch? Er meinte ein guter Programmierer
übergibt einen Pointer auf die Enum an die Funktion.
Ich könnte diese Aussage nachvollziehen wenn ein ENUM extrem groß wäre
und bei einem Call-By-Value viele Daten um-kopiert werden müssten.
Allerdings ist laut ISO/ANSI C ein ENUM maximal sizeof(int), also bei
meinem System 32 Bit. Eben so groß ist ein Pointer auf eine Enum
Variable. Es dürfte also keinen Performance unterschied machen.
Ich habe mir einige Coding Standards angeschaut... MISRA C und diverser
andere... keiner weißt darauf hin dass ein ENUM nicht als return value
verwendet werden sollte.
Ich programmiere schon seit einigen Jahren und hab mich deshalb über
diese Aussage doch sehr gewundert... Ich wollte die Sache schon auf sich
beruhen lassen als ich die gleiche Aussage aus anderem Mund zu hören
bekam. Seit dem hat mich die Sache nicht ruhig gelassen und deshalb
wende ich mich an euch, quasi die geballte Kompetenz wenn es um Embedded
Systems geht. =)
Eventuell verstehe ich hier ja grundsätzlich etwas nicht...
Mmh, das einzige Argument, dass mir jetzt einfallen würde:
Du verspielst die Möglichkeit, auf dem üblichen Weg (return-Wert) einen
Fehlercode aus der Funktion zurückgeben zu können. Das ist aber nicht
nur bei ENUMs als Rückgabewert so, sondern prinzipiell bei allen
Rückgabewerten (außer signed Typen).
Wenn es andere Gründe gibt interessieren die mich auch sehr.
Lord Ziu schrieb:> Du verspielst die Möglichkeit, auf dem üblichen Weg (return-Wert) einen> Fehlercode aus der Funktion zurückgeben zu können.
... sofern die Fehlercodes nicht im enum enthalten sind oder der enum
sogar nur dafür dient, Fehlercodes zu transportieren.
Rufus t. Firefly schrieb:> Lord Ziu schrieb:>> Du verspielst die Möglichkeit, auf dem üblichen Weg (return-Wert) einen>> Fehlercode aus der Funktion zurückgeben zu können.>> ... sofern die Fehlercodes nicht im enum enthalten sind oder der enum> sogar nur dafür dient, Fehlercodes zu transportieren.
Das ist natürlich richtig. Aber da man in den meisten Fällen auf mehrere
mögliche Fehlerfälle reagieren muss (und daher unterschiedliche
Fehler-Rückgabewerte benötigt) macht es für mich wenig Sinn, die
Fehlerfälle zusammen mit gültigen Rückgabe-Ergebnissen in einem ENUM zu
mischen.
Trotzdem fällt mir kein anderer Grund ein, warum man ein ENUM nicht als
Rückgabewert benutzten sollte.
Das einzige was man enums als schlechte Nachrede anhängen könnte ist,
dass sie in C nur so 'halbe Datentypen' sind. Nicht Fisch, nicht
Fleisch. Zum Sterben zu viel, zum Leben zu wenig.
enum haben in C dokumentatorischen Wert, werden aber ansonsten wie int
behandelt. D.h. man kann an eine enum FARBE Variable jederzeit 42
zuweisen und das ist ja dann nicht Sinn der Sache.
Aber abgesehen von diesem grundsätzlichen Manko, ist mir auch nicht
klar, warum man keine enum aus einer Funktion returnieren soll. Ich
wüsste auch keinen irgendwie gerateten Grund oder eine mögliche Falle,
die man hier umschiffen wollte.
Also ich halte diese Behauptung auch für Quatsch. Was soll man den sonst
(formal) zurückgeben? int? AFAIK ist doch ein enum-Wert intern nichts
anderes als ein int. enum als Rückgabe kann da doch eigentlich nicht
schaden, zeigt dem Nutzer dafür an, was hier zu erwarten ist.
Und kann ein C-Profi einem C-Amateur wie mir einmal erklären, was ein
"Pointer auf die Enum" ist, die man da angeblich übergeben (können)
soll?
Also, ein paar Gedanken von mir:
Enum-Typen haben m.E. als einziges Problem, dass es sich um C-Typen
handelt, du also keinen Einfluss auf die Größe hast. Wenn ich mich recht
erinnere, ist noch nicht einmal klar, ob es sich um signed oder unsigned
Werte handelt.
D.h. wenn Du extrem mit dem Speicher geizen musst, kann es ungeschickt
sein, wenn eine Ampelsteuerung 16 Bit (oder, wie bei Dir 32 Bit)
verbrät, um ROT, GELB oder GRÜN zu speichern.
Ein echtes NoNo sind Enum-Werte in Strukturen, die im EEPROM abgelegt
oder serialisiert und verschickt werden sollen. Irgendjemand wird einmal
die Compilereinstellungen von "Zeit Optimieren" auf "Speicher
Optimieren" umstellen und plötzlich ist Dein Enum statt 4 Bytes nur noch
eines groß.
Viele Qualitätsregelwerke verbieten außerdem Casts von Integerwerten auf
Enums, so dass man sich für die Konvertierung eigene Routinen mit
switch-Konstrukten schreiben muss, die völlig dämlich aussehen.
Es gibt also schon ein paar Sachen, die man im Hinterkopf behalten
sollte, aber generell Enums zu verbieten halte ich auch für falsch.
Schließlich ist es so schon schwer genug, in C gut strukturierten Code
zu schreiben.
Grüße,
Stefan
P.S.: Das mit dem Pointer auf Enum adressiert die oben beschriebenen
Probleme gar nicht, ist also m.E. Mumpitz.
Detlev T. schrieb:> Und kann ein C-Profi einem C-Amateur wie mir einmal erklären, was ein> "Pointer auf die Enum" ist, die man da angeblich übergeben (können)> soll?
Gemeint ist höchst wahrscheinlich
anstelle von
1
enumxyzfoo()
2
{
3
...
4
5
returnvalue;
6
}
das hier zu verwenden
1
voidxyz(enumxyz*retValue)
2
{
3
...
4
5
*retValue=value;
6
}
ok. einen "Vorteil" hat es. Es ist nicht mehr so einfach möglich zu
ignorieren, dass die Funktion etwas zurückliefert. Zumindest muss der
Aufrufer eine Variable für den Return-Wert haben, deren Adresse er
übergeben kann.
Das hat aber mehr mit Programmierdisziplin zu tun, als mit irgendwelchen
technischen Gründen.
Rainer K. schrieb:> Er meinte ein guter Programmierer> übergibt einen Pointer auf die Enum an die Funktion.
Das ist Käse. Wenn dann ein Pointer auf eine Enum Variable (die dann
global sein müßte). Und wenn immer es geht würde ich globale Variablen
tunlichst vermeiden.
> ein Kommilitone sagte mir das ich unter keinen Umständen eine> Enumeration als Rückgabewert verwenden soll und schon garnicht bei Code> für embedded systeme.
Meiner Erfahrung nach kommen solche "merkwürdigen" Aussagen meist von
Leuten eigentlich immer nur auf dem gleichen Target und mit dem gleichen
Compiler gearbeitet haben und nichts anderes kennen (ich habe auch schon
pauschal gehört, dass Optimierungsstufe 3 fehlerhaften Code erzeugt). In
der Kombination gibt es dabei vielleicht tatsächlich Probleme mit enums.
Oder irgendeine Codierrichtlinie verbietet pauschal die Rückgabe von
enums.
Einen Grund gibt es aber afaik nicht. Ich bevorzuge eher enums da einige
statische Code-Analysen in der Lage sind wilde Mischungen zwischen
nativen Typen und Enums (z.B. in Berechnungen) anzumeckern die leicht
unbeabsichtigt entstehen können. Es ist z.B. schöner an eine Funktion
ein enum Parameter zu übergeben anstatt int werte...
klaus schrieb:> Das ist Käse. Wenn dann ein Pointer auf eine Enum Variable (die dann> global sein müßte).
Wieso das? Sie muss doch nur im Kontext des Aufrufers gültig sein, kann
also genausogut eine automatische Variable sein.
> Und wenn immer es geht würde ich globale Variablen tunlichst vermeiden.
Das ist unbestritten oft sinnvoll. Es gibt Ausnahmen, aber die sind
selten.
Rufus t. Firefly schrieb:> Wieso das? Sie muss doch nur im Kontext des Aufrufers gültig sein, kann> also genausogut eine automatische Variable sein.
Müßtest du dann aber als irgendwie Argument der aufzurufenden Funktion
mitgeben. In diesem Fall sehe ich aber in der Rückgabe dieses Pointers
(und davon war ja die Rede) keinen Sinn mehr.
Rufus t. Firefly schrieb:> Wieso das? Sie muss doch nur im Kontext des Aufrufers gültig sein, kann> also genausogut eine automatische Variable sein.
Allerdings sind adressierbare lokale Variablen in vielen Fällen
erheblich teurer als welche, von der keine Adresse benötigt wird.
A. K. schrieb:> Allerdings sind adressierbare lokale Variablen in vielen Fällen> erheblich teurer als welche, von der keine Adresse benötigt wird.
Magst Du das ein bisschen elaborieren?
klaus schrieb:> Rufus t. Firefly schrieb:>> Wieso das? Sie muss doch nur im Kontext des Aufrufers gültig sein, kann>> also genausogut eine automatische Variable sein.>> Müßtest du dann aber als irgendwie Argument der aufzurufenden Funktion> mitgeben.
Genau darum geht es doch: Um Komminkation einer Funktion mit ihrer
Aussenwelt. Wenn du sowieso eine globale Variable hast, brauchst du dir
über Rückgabe oder Argumenttypen keinen Gedanken mehr machen (Dafür dann
über 100-tausend andere Dinge, die jetzt aber hier nicht hergehören.
Globale Variablen sind ein anderes Thema)
> In diesem Fall sehe ich aber in der Rückgabe dieses Pointers> (und davon war ja die Rede) keinen Sinn mehr.
Ich denke du hast das falsch verstanden.
Es wird kein Pointer zurückgegeben, sondern anstelle eines
Rückgabewertes soll ein Pointer in die Funktion hineingegeben werden. So
lautet zumindest der Vorschlag. Warum auch immer.
Rufus t. Firefly schrieb:> A. K. schrieb:>> Allerdings sind adressierbare lokale Variablen in vielen Fällen>> erheblich teurer als welche, von der keine Adresse benötigt wird.>> Magst Du das ein bisschen elaborieren?
Da sag ich nur:
Premature optimization is the root of all evil.
Ansonsten als Hinweis: im neuen C++ Standard kann man die
Zuweisungkompatibilität zu Integer abschalten, wenn man strikte
Enumerations nutzt. Im obigen Falle dann
1
typedefenumclass{
2
ROT=1,
3
BLAU,
4
GELB,
5
ORANGE
6
}FARBE;
Alternativ: enum struct. Zuweisungskompatible Typen sind nun nur noch
die auch oben deklarierten Member der Aufzählung. Dafür ist man aber
wiederrum gezwungen den Namen der Aufzählung mit anzugeben (es wird wie
eine Art eigener Namespace umgesetzt), also
1
FARBElFarbe(FARBE::BLAU);
Aber das nur als Hinweis. Es ist eh C++ und somit hier wohl auch nicht
von belangen...
Rufus t. Firefly schrieb:>> Allerdings sind adressierbare lokale Variablen in vielen Fällen>> erheblich teurer als welche, von der keine Adresse benötigt wird.>> Magst Du das ein bisschen elaborieren?
Variablen mit Adresse landen zwingend im Speicher, wenn lokal dann auf
dem Stack. Andere Skalare landen oft in Registern. Speicherzugriffe sind
deutlich teurer. Je nach Architektur kann zudem deutlich Aufwand für den
Stackframe entstehen.
Martin schrieb:> Premature optimization is the root of all evil.> [Donald Knuth]
Knuth in Ehren, aber den Brunnen erst zudecken wenn das Kind schon
reingefallen ist, das ist auch kein genialer Ansatz. Ich ziehe es vor,
erst nachzudenken und dann zu implementieren. Und dazu gehört auch (aber
nicht nur), die Konsequenzen vorher zu bedenken. Ich bin sicher, dass es
auch dazu einen Spruch von ihm gibt.
Es gib Leute, die APIs/Libs weitgehend mit teilweise komplexen
Funktionen und übergebenen Strukturen realisieren. Paradebeispiel dafür
ist die Firmware-Lib der STM32. Wenn man sowas mal definiert hat, dann
ist das Kind im Brunnen und nachträgliche Optimierung scheidet der
Kompatibilität wegen aus. Zudem wird es dadurch auch noch umständlicher
und zeilenfressender im Umgang.
A. K. schrieb:> Variablen mit Adresse landen zwingend im Speicher
Hups, magst du dafür mal eine Quelle nennen? Es lassen sich doch auch
Pointer auf den Stack erstellen?
Nico
Nico22 schrieb:> Hups, magst du dafür mal eine Quelle nennen? Es lassen sich doch auch> Pointer auf den Stack erstellen?
Klar doch. Aber ist das etwa kein Speicher?
Rainer K. schrieb:> Er meinte ein guter Programmierer> übergibt einen Pointer auf die Enum an die Funktion.
Sag dem Kerl, ein "guter Programmierer" stellt nicht einfach
irgendwelche Behauptungen auf ohne diese z.B.durch Angabe eines
Beispiels o.ä. verifizierbar zu machen. Wenn man schon einen Satz so
beginnen hört, möchte man den Rest doch gar nicht mehr hören - diesen
Umstand nutzen die Möchtegerns aus um sich wichtig zu machen.
Just my 2 Cents