Oh, oh, ich werde mal wieder gezwungen, Code von anderen Menschen zu
lesen.
Gefunden in einem der wenigen AVR-kompatiblen Codeschnipseln zum VL53L1X
(ich glaube, dieser Teil kommt ursprünglich von ST)
Horst S. schrieb:> Liegt das jetzt daran, dass das packed-Attribut nicht auf allen Systemen> den Byte-Datentyp garantiert?
Packed braucht man, wenn überhaupt, nur für Structs.
Enum war schon immer vom Typ int.
Enum ist auch nur eine andere Schreibweise für Defines von Konstanten.
Der Vorteil ist, es zählt selber aufwärts, man kann also nicht
versehentlich Defines doppelt vergeben.
Anderer Frank schrieb:> Und sie sind typsicher.
Aber leider nicht in C. Der einzige Vorteil ist daß enums
selbstdokumentierend sind, man muß nicht herumrätseln um zu sehen welche
Werte an der Stelle erwartet werden.
Anderer Frank schrieb:> Und sie sind typsicher.
In C ist da kein Unterschied, eine Konstante ist default auch vom Typ
int.
Bei einem Define kann man allerdings einen anderen Typ spezifizieren,
ein Enum bleibt immer signed int.
Horst S. schrieb:> typedef uint8_t VL53L1_DistanceModes;>> #define VL53L1_DISTANCEMODE_SHORT ((VL53L1_DistanceModes) 1)> #define VL53L1_DISTANCEMODE_MEDIUM ((VL53L1_DistanceModes) 2)> #define VL53L1_DISTANCEMODE_LONG ((VL53L1_DistanceModes) 3)
Ehrlich gesagt habe ich so ein define (statt enum) noch nie gesehen.
Wie wird es denn dann eingesetzt? Vielleicht erklärt das, warum es kein
enum ist.
Daniel V. schrieb:> Ehrlich gesagt habe ich so ein define (statt enum) noch nie gesehen.
Ist schon recht ungewöhnlich.
Wenn ich Typen definiere, kommt bei mir immer _t ans Ende.
Statt "(VL53L1_DistanceModes) 1" sollte "1U" eigentlich reichen.
Peter D. schrieb:> Daniel V. schrieb:>> Ehrlich gesagt habe ich so ein define (statt enum) noch nie gesehen.>> Ist schon recht ungewöhnlich.> Wenn ich Typen definiere, kommt bei mir immer _t ans Ende.> Statt "(VL53L1_DistanceModes) 1" sollte "1U" eigentlich reichen.
Die Unterschiede zwischen enum und #define sind mir bekannt.
Allerdings verstehe ich immer noch nicht den Einsatz des oben genannten
#defines.
Wie kann ich das denn einsetzen?
Durch ein #define wird doch nur eine Textersetzung vorgenommen, und wenn
dann irgendwo
1
...((VL53L1_DistanceModes)1)...
steht, was soll das bringen????
Deswegen meine Frage, wie werden die oben genannten Defines im Code
konkret eingesetzt? Ich stehe auf dem Schlauch :-(
Peter D. schrieb:> Enum ist auch nur eine andere Schreibweise für Defines von Konstanten.
Das ist leider eben nicht so. C kennt keine echten Konstanten, nur
Variablen die man nicht verändern darf ("const"). Makros ("#define")
sind Textersetzungen und keine Konstanten.
Wenn man so etwas hat:
weil man nicht immer im Kopf hat welche Namen schon für Makros vergeben
sind (insb. wenn man große Libraries verwendet welche Tausende davon
definieren) gibt es lustige Compiler-Fehler. Hartgesottene
C-Programmierer finden das in Ordnung weil Großbuchstaben nur für Makros
vorenthalten sein sollen - das ist m.M.n. ein schlechter Schutz und auch
nicht immer schön. Bei einer Konstanten oder einem enum passiert das
nicht:
1
typedefenum
2
{
3
VL53L1_DISTANCEMODE_SHORT=1,
4
VL53L1_DISTANCEMODE_MEDIUM=2,
5
VL53L1_DISTANCEMODE_LONG=3
6
}VL53L1_DistanceModes;
7
// ... furchtbar viel Code ...
8
intmain(){
9
intVL53L1_DISTANCEMODE_SHORT=7;
10
}
funktioniert. Daher sind enums und Konstanten soweit möglich zu
bevorzugen. Konstanten kann man nur leider nicht z.B. als Array-Größen
nutzen, weil sie in C nicht Compile-Time-konstant sind (in C++ schon).
Immerhin wurde daran gedacht explizit den Typ VL53L1_DistanceModes mit
anzugeben. Oft sieht man so etwas:
1
#define VL53L1_DISTANCEMODE_SHORT 1
2
3
voidfoo(VL53L1_DistanceModes){}
4
voidfoo(int){}
5
6
intmain(){
7
foo(VL53L1_DISTANCEMODE_SHORT);
8
}
Welche Funktion wird hier aufgerufen? Betrifft natürlich nur C++, C
achtet nicht so auf Typen.
Ein Vorteil von Konstanten:
geht weder mit enums noch Makros.
In C++ kann man es sich leicht machen und immer "const" nehmen. In C
muss man zwischen enum und const abwägen und im Notfall kann man Makros
nutzen.
Dr. Sommer schrieb:> typedef enum> {> VL53L1_DISTANCEMODE_SHORT = 1,> VL53L1_DISTANCEMODE_MEDIUM = 2,> VL53L1_DISTANCEMODE_LONG = 3> } VL53L1_DistanceModes;> // ... furchtbar viel Code ...> int main () {> int VL53L1_DISTANCEMODE_SHORT = 7;> }funktioniert.
Gibt aber ne Warnung, ist also bad style:
enum.c:9: warning: declaration of 'VL53L1_DISTANCEMODE_SHORT' shadows a
global declaration
Peter D. schrieb:> Gibt aber ne Warnung, ist also bad style:
Besser als kryptische Fehler dank Präprozessor. Und mit welchem Compiler
und Optionen? Mein GCC gibt mit -Wall -Wextra -pedantic gar nix aus.
Daniel V. schrieb:> Ehrlich gesagt habe ich so ein define (statt enum) noch nie gesehen.> Wie wird es denn dann eingesetzt? Vielleicht erklärt das, warum es kein> enum ist.
Die Konstanten beschreiben hier einen Registerinhalt des VL53L1X-Sensors
(über I2C angesprochen). Ich denke mal, der Typedef von
VL53L1_DistanceModes auf einen uint8_t stellt einfach nur den Byte-Typ
sicher. (Man spart sich das Casten beim Schreiben auf's entsprechende
TWI-Register).
Peter D. schrieb:> Packed braucht man, wenn überhaupt, nur für Structs.> Enum war schon immer vom Typ int.
Wenn ich einen Enum als packed deklariere, den wiederum in einer
packed-Struktur verwende, ist das Feld 1 Byte groß, zumindest beim AVR.
Zitat aus der gcc-Doku
(https://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Type-Attributes.html)
"packed: This attribute, attached to an enum, struct, or union type
definition, specified that the minimum required memory be used to
represent the type."
Ob das IMMER ein Byte ist? Da schweigt die Doku.
Ok
> #define VL53L1_DISTANCEMODE_SHORT ((VL53L1_DistanceModes) 1)
soll ein Typecast sein, jetzt hab ich es geschnallt.
Die Lücke zur Zahl hat mich irritiert.
Insofern halte ich das #define für besser als das enum, weil man nicht
noch zusätzlich casten muss, das ist schon "eingebaut".
Peter D. schrieb:> -Wshadow
Na, die ist sonst aber nie an. Es gibt alle Möglichen
-Wirgendwas-Optionen die einem bei praktisch jedem Code mit Warnungen
überschütten... Deswegen alles umzubauen ist unpraktikabel. Shadowing
ist schon okay, wenn man in einem lokalen Scope dann doch mal auf das
globalere zugreifen will kann man das lokale immer noch umbenennen. Das
tut dann nicht weh, und in allen anderen Fällen merkt man gar nichts und
alles ist gut.
Horst S. schrieb:> Peter D. schrieb:>> Packed braucht man, wenn überhaupt, nur für Structs.>> Enum war schon immer vom Typ int.> Wenn ich einen Enum als packed deklariere, den wiederum in einer> packed-Struktur verwende, ist das Feld 1 Byte groß, zumindest beim AVR.
Es geht ja nicht allein um die Größe, es könnte ja auch ein sint8_t sein
(oder in float oder...), dann haut das mit den enum (packed oder nicht)
wieder nicht hin.
Dr. Sommer schrieb:> Immerhin wurde daran gedacht explizit den Typ VL53L1_DistanceModes mit> anzugeben. Oft sieht man so etwas:>
1
>#defineVL53L1_DISTANCEMODE_SHORT1
2
>
3
>voidfoo(VL53L1_DistanceModes){}
4
>voidfoo(int){}
5
>
6
>intmain(){
7
>foo(VL53L1_DISTANCEMODE_SHORT);
8
>}
> Welche Funktion wird hier aufgerufen? Betrifft natürlich nur C++, C> achtet nicht so auf Typen.
Gar keine. Der Compiler meckert. In C gibts keine Funktionsüberladung.
Dr. Sommer schrieb:> Keiner N. schrieb:>> Gar keine. Der Compiler meckert. In C gibts keine Funktionsüberladung.>> Deswegen steht da ja auch "nur C++".
lies mal den ganzen Text bitte. Also den von Dr. Sommer
Ich verwende für sowas immer Enums. Ich kompiliere meinen Code auch
immer mit "-std=c99 -Wall -Wextra -pedantic -Werror". Hat bei gcc
folgende Vorteile:
1) Der Compiler warnt, wenn man in einem Switch nicht entweder alle
Optionen abdeckt, oder einen default case hat (-Wswitch): (mit
-Wswitch-enum kann man ihn auch mit default case warnen lassen)
Leider hat der gcc keine Option zum warnen bei Zuweisungen oder beim
Vergleich 2er enum variablen, aber vielleicht kommt das ja noch. clang
scheint dafür massenhaft Optionen zu haben, z.B. "-Wassign-enum".
Ansonsten können auch viele linter vor derartigen Dingen warnen.
Dr. Sommer schrieb:> Shadowing> ist schon okay
Für mich nicht. Ist ne prima Methode, sich ins Knie zu schießen.
Für mich muß der gleiche Name auch immer die gleiche Bedeutung haben.
Daher kann ich mich auch nicht mit der Überlagerung von C++ anfreunden.
Blau muß immer blau bedeuten und rot immer rot.
Mir reicht die Mehrdeutigkeit in der menschlichen Sprache und die daraus
entstehenden Mißverständnisse völlig. Das muß ich nicht auch noch bei
ner Programmiersprache haben.
Peter D. schrieb:> Für mich nicht. Ist ne prima Methode, sich ins Knie zu schießen.
Was machst du dann, wenn du eine Library programmiert hast, die von 100
Programmen genutzt wird, und du einen neuen Bezeichner hinzufügst?
Prüfst du ob alle 100 Programme den nirgends nutzen?
Sowas hab ich total gern:
Dr. Sommer schrieb:> Was machst du dann, wenn du eine Library programmiert hast, die von 100> Programmen genutzt wird, und du einen neuen Bezeichner hinzufügst?> Prüfst du ob alle 100 Programme den nirgends nutzen?
Peter programmiert nur in Kleinen. Gängige Softwarekonzepte sind im
fremd und er lehnt diese grundsätzlich ab.
Peter D. schrieb:> Packed braucht man, wenn überhaupt, nur für Structs.> Enum war schon immer vom Typ int.
Falsch. Bin ich erst neulich wieder drauf reingefallen.
1
enumledstatus
2
{
3
OFF,DIM,DIM_FLASH,FLASH,ON
4
}__attribute__((packed));
5
6
// ...
7
enumledstatusleds[2*NLEDS];
8
9
// ...
10
// over temperature: flash-dim all LEDs
11
memset(leds,DIM_FLASH,sizeofleds);
Das Ganze funktionierte ohne „__attribute__((packed))“ nicht wie
gewünscht (nur die Hälfte aller LEDs wurde passend gesetzt), mit
dagegen schon.
Das widerlegt ganz eindeutig deine Aussage: mit GCC und „packed“
passt ein enum in 8 Bit, sofern er hinreichend wenige Elemente
enthält.
Horst S. schrieb:> Liegt das jetzt daran, dass das packed-Attribut nicht auf allen Systemen> den Byte-Datentyp garantiert?
Anders: dieses Attribut ist eine Eigenheit des GCC; andere Compiler
können das entweder gar nicht, oder machen sowas vielleicht über ein
Pragma.
Insofern ist der Originalcode zumindest portabel.
Wenn ich weiß, dass ich eh' nur für den GCC schreibe, ist mir das
natürlich egal.
Dr. Sommer schrieb:> Was machst du dann, wenn du eine Library programmiert hast, die von 100> Programmen genutzt wird, und du einen neuen Bezeichner hinzufügst?
Entweder der Bezeichner soll von anderen genutzt werden, dann stelle ich
ein Prefix vor alle Bezeichner. Wäre zwar schön, wenn es Namespaces
gäbe, aber man kann halt nicht alles haben. Andernfalls definiere ich es
dort wo ich es brauche als static. Für gewisse dinge habe ich jenachdem
auch Interfaces & Funktionen, mit denen ich Implementationsspezifische
locale Instanzen registriere, die wiederum als static definiert wurden,
so dass ich die Implementationen global nutzen kann, ohne ein
zusätzliches Symbol einzuführen.
Peter D. schrieb:> Enum ist auch nur eine andere Schreibweise für Defines von Konstanten.
Nicht wirklich.
> Der Vorteil ist, es zählt selber aufwärts, man kann also nicht> versehentlich Defines doppelt vergeben.
Das stimmt zwar, ist aber nicht der wesentliche Punkt. Bei ENUMs geht es
explizit darum, daß es nicht interessiert, welcher Zahlenwert sich
hinter den deklarierten Bezeichnern jeweils verbirgt. Weil jede
Operation mit einer ENUM Variable entweder die Zuweisung oder der
Vergleich mit einem der erlaubten Bezeichner ist. Alles andere ist in
Verbindung mit ENUMs "bäh".
An dieser Stelle ist das aber gar nicht der Fall. Man will nicht nur 3
verschiedene Fälle sicher unterscheiden, man will auch exakt die
angegebenen Zahlenwerte dafür haben (mutmaßlich damit man sie in die
entsprechenden Register schreiben kann; ich kenne diesen Sensor nicht im
Detail). Und dann ist ein #define das Mittel der Wahl.
ENUM ist IMNSHO ein absolut überbewertetes Konzept.
Dr. Sommer schrieb:> Wenn man so etwas hat:> #define VL53L1_DISTANCEMODE_SHORT> ((VL53L1_DistanceModes) 1)> // ... furchtbar viel Code ...> int main () {> int VL53L1_DISTANCEMODE_SHORT = 7;> }> weil man nicht immer im Kopf hat welche Namen schon für Makros> vergeben sind
Das kann nicht passieren. Bezeichner in GROSSBUCHSTABEN sind immer
Makros, niemals Variablen. Wer so schlampigen Code schreibt, bettelt
geradezu um
> lustige Compiler-Fehler
Axel S. schrieb:> ENUM ist IMNSHO ein absolut überbewertetes Konzept.
Bei C++ funktionieren sie viel besser als bei C, wo sie historisch halt
nur eine Alternative zu "int" waren. Dort sind sie dann wirklich
typsicher.
Früher war das wesentliche Argument für enum in C noch, dass der
Debugger dann die Namen auflösen kann; seit DWARF es schafft, dass
man auch die Definition von Makros in den Debuginformationen
reflektieren
kann, ist dieser Vorteil jedoch passe.
Axel S. schrieb:> Bezeichner in GROSSBUCHSTABEN sind immer> Makros, niemals Variablen.
Schwacher Schutz. In vernünftigen Sprachen kann man sich von
Sprachmitteln statt von Schreib-Konventionen helfen lassen.
Axel S. schrieb:> Wer so schlampigen Code schreibt
Wie würdest du denn eine Klasse oder Funktion nennen, die die
"RCC"-Hardware behandelt? Überall in der Doku heißt das Teil "RCC".
"Rcc"? "rCC"? "rcc"? "DiesIstKeinMakro_RCC"? Alles hässlich. Aber "RCC"
ist leider vorbelegt von einem elenden Makro, und die kann man nicht
shadowen.
Axel S. schrieb:> ENUM ist IMNSHO ein absolut überbewertetes Konzept.
Enum ist ein extrem nützliches Konzept in typsicheren Sprachen. Schau
Dir nur mal an was zum Beispiel Sprachen wie Rust damit zuwege bringen
können, die erschlagen damit auch gleich noch Nullpointer und
Fehlerbehandlung mit einer Klappe!
Nur leider hatten die Erfinder von C ganz andere Prioritäten und auch
diejenigen die all die Jahre den C Standard bis heute weitergepflegt
haben hatten nie den Willen oder die Kraft die Karre mal ordentlich
herumzureißen.
Dr. Sommer schrieb:> z.B. bei der berühmten Entprellroutine:> int main(void) {> const int i = 7;> debounce( PINB, PB1); // Warnung bei -Wshadow> }(i ist jetzt kein so seltener Bezeichner)
Man kann sich vieles zusammen konstruieren.
i,j,k sind für mich als Schleifenzähler reserviert, das debounce-Macro
macht aber nur in einer Endlosschleife ohne Schleifenzähler Sinn.
Daher kann ich ruhig -Wshadow drin lassen.
Bernd K. schrieb:> Nur leider hatten die Erfinder von C ganz andere Prioritäten
Vor allem: ganz andere Möglichkeiten.
64 KiB für Code und 64 KiB für Daten (sofern sie glücklich genug
waren, den Hack für "Split I&D" in ihrer PDP-11 nachgerüstet zu
haben).
> und auch> diejenigen die all die Jahre den C Standard bis heute weitergepflegt> haben hatten nie den Willen oder die Kraft die Karre mal ordentlich> herumzureißen.
Es wäre halt inkompatibel geworden. Daher konnte man das in C++ anders
durchsetzen.
Was man bei all dem Gemecker über deren Konzepte und den C-Standard
mal nicht vergessen sollte: wenn sich bei der Standardisierung
irgendwelche Informatiker durchgesetzt hätten, die „schöne Konzepte“
als Primat haben statt sinnvoller Rückwärtskompatibilität, dann wäre
C gewiss nicht auf der Position, auf der es sich heute befindet.
Dass man für Rückwärtskompatibilität Abstriche machen muss, dürfte
völlig
außer Zweifel stehen. So gesehen ist die fortlaufende Standardisierung
einer derartigen Sprache halt stets ein Spagat.
Peter D. schrieb:> i,j,k sind für mich als Schleifenzähler reserviert
1
IMPLICIT INTEGER I-N
:-)
Allerdings halte ich das Argument dennoch für reichlich konstruiert;
wer
1
#define i <irgendwas>
schreibt, sollte nochmal zurück in die C-Grundschule gehen.
Dass C++ in vielerlei Hinsicht besser aufgestellt ist, dafür brauchen
wir hier nicht jedesmal einen Dr. Sommer … das war jedoch gar nicht die
Frage des TO.
Peter D. schrieb:> Man kann sich vieles zusammen konstruieren.> i,j,k sind für mich als Schleifenzähler reserviert, das debounce-Macro> macht aber nur in einer Endlosschleife ohne Schleifenzähler Sinn.
Na, man kann sich auch kuriose Regeln konstruieren! Und was ist mit
"flag"? Das tritt natürlich im Zusammenhang mit debouncing auch nicht
auf?
Dr. Sommer schrieb:> Na, man kann sich auch kuriose Regeln konstruieren!
Hör doch bitte einfach wieder auf mit deiner Missioniererei. Sie passt
überhaupt nicht in diesen Thread. Der TO wollte nämlich überhaupt nicht
wissen, ob das in anderen Sprachen anders, besser oder schlechter wäre –
nur, falls dir sein Anliegen entgangen sein sollte.
Dass man diverse Schwächen von C durch coding style guidelines
ausgleicht, ist einfach so. Eine dieser völligen Grundregeln ist,
dass Makros immer GROSS geschrieben werden, Variablen nie. Wenn man
sich an diese Regel hält, dann ist es wurscht, ob da nun "i" oder
"flag" steht: es ist eine Variable, kein Makro.
Ich fasse das mal für mich zusammen:
Wenn ich die Defines bei der Übernahme der Quellen im Controller zu
Enums umschreibe, verliere ich keine Kompatibilität, wenn ich in den
entsprechenden Funktionen den Cast einfüge. Unterm gcc läuft's auch
nicht langsamer, weil der Schlauberger seine Typen kennt und den Cast
uint8_t->uint8_t wegoptimiert?!?
Jörg W. schrieb:> Der TO wollte nämlich überhaupt nicht> wissen, ob das in anderen Sprachen anders, besser oder schlechter wäre –> nur, falls dir sein Anliegen entgangen sein sollte.
Interpretier mich nicht. Ich hab da so meine Hintergedanken, die kannst
Du doch noch gar nicht wissen :-)
Für die PC-Seite (C#) habe ich mittlerweile 'nen rudimentäres
Übernahmetool, das mir aus den Enums und Strukturen des AVR-Projektes
entsprechende Datentypen nebst Wandlungsroutinen für die Übertragung
zusammenbastelt. Da stehen mir die Defines echt im Weg.
Wenn ich das also im Zusammenhang (Sensor/Controller/PC) betrachte,
scheint mir bezüglich Kompatibilität eher der Enum der kleinste
gemeinsame Nenner.
Horst S. schrieb:> Für die PC-Seite (C#) habe ich mittlerweile 'nen rudimentäres> Übernahmetool, das mir aus den Enums und Strukturen des AVR-Projektes> entsprechende Datentypen nebst Wandlungsroutinen für die Übertragung> zusammenbastelt.
In diesem Falle bist du doch sowieso komplett Abhängig von den
Implementierungsdetails der jeweiligen Systeme. Da spielt die
Portabilität des Originalcodes keine große Geige.
> Interpretier mich nicht.
Wenn du Hintergedanken hast, die über deine initiale Frage hinausgehen,
wäre es natürlich sinnvoll, diese gleich in der Frage mit zu erwähnen.
Jörg W. schrieb:> Wenn du Hintergedanken hast, die über deine initiale Frage hinausgehen,> wäre es natürlich sinnvoll, diese gleich in der Frage mit zu erwähnen.
Die eigentliche Frage (ist mir auch erst im Verlauf des Threads
aufgegangen)
"Sind Enums über Systemgrenzen hinweg überhaupt zu gebrauchen"
hab ich mir, ehrlich gesagt, nie gestellt. Wenn doch, hätte ich spontan
geantwortet: "Natürlich!"
Allerdings auch nur mit dem Wissen, dass einem Element ein Wert
zugewiesen werden kann (in diesem Fall muss). Wer das nicht tut, hat
noch nie einen Kollegen gehabt, der die Elemente eines Enums mit Genuss
alphabetisch sortiert (und neue Elemente auch mal in der Mitte einfügt).
Das ist ein Vertrag, der dem #define-Makro in nichts nachsteht.