Forum: Mikrocontroller und Digitale Elektronik #define auf enum


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich bin gestern auf ein Problem gestoßen, dessen ich mir vorher nicht 
bewusst war. Das Programm sah sinngemäß so aus:
1
enum choice_e
2
{
3
    choice_a,
4
    choice_b,
5
};
6
7
#define MYCHOICE choice_b
8
9
10
/* Funktion zum Testen von Sachen. */
11
char runToShow(void)
12
{
13
    volatile char a;
14
15
    #if( MYCHOICE == choice_a )
16
        a = 'a';
17
18
    #elif( MYCHOICE == choice_b )
19
        a = 'b';
20
21
    #else
22
        #error "Invalid choice"
23
24
    #endif
25
26
    volatile char b = 'c';
27
28
    return a;
29
}
Die beiden volatile-Variablen haben nur den Zweck, einen Breakpoint im 
Debugger setzen zu können.

Ich nehme mal an, dass hier jeder weiss, welchen Wert die Funktion 
zurückgibt. Aber warum gibt sie diesen Wert zurück?

: Bearbeitet durch User
von Martin (Gast)


Lesenswert?

Walter T. schrieb:

> Ich nehme mal an, dass hier jeder weiss, welchen Wert die Funktion
> zurückgibt. Aber warum?

Weil sie die Sprache C beherrschen.

von Rolf M. (rmagnus)


Lesenswert?

Walter T. schrieb:
> Aber warum?

Weil der Präprozessor nur Präprozessor-Direktiven auswertet. Enums 
kommen erst später, im eigentlichen Compiler, dran.

von Walter T. (nicolas)


Lesenswert?

Ich habe die Frage angepasst, weil ich vergessen habe, dass hier mehr 
Compiler als Menschen unterwegs sind.

Dass Enums erst später ausgewertet werden, ist mir auch bewusst 
geworden. Aber warum ist der Wert von "choice_a" für den Präprozessor 
überhaupt definiert und er wandert nicht in den Fehlerzweig?

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Der Präprozessor macht nach der Makro-Ersetzung bei == einen numerischen 
Vergleich der Werte auf beiden Seiten. Zuerst wird MYCHOICE durch 
choice_b ersetzt. Alle Identifier, die keine Makro-Namen sind, 
evaluieren zu 0. Da es kein Makro namens choice_b gibt, wird da also 0 
draus. Ein Makro namens choice_a gibt es ebenfalls nicht, also kommt da 
auch 0 raus. Damit sind beide Seiten gleich. Das würde übrigens auch auf 
das #elif zutreffen, aber das kommt ja gar nicht erst dran.

Walter T. schrieb:
> Ich habe die Frage angepasst, weil ich vergessen habe, dass hier mehr
> Compiler als Menschen unterwegs sind.

Sorry, ich hab meinen Text jetzt aus Sicht des Compilers (genau genommen 
des Präprozessors) geschrieben. Ich hoffe, das ist trotzdem ok…

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Hallo Rolf,
bitte entschuldige die missverständliche Formulierung. Auf Deine Antwort 
war die Bemerkung nicht bezogen, sondern auf die davor.

Danke, die Erklärung klingt so, dass die Chancen sehr gut sind, dass ich 
sie mir merken kann.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Walter T. schrieb:
> Aber warum ist der Wert von "choice_a" für den Präprozessor überhaupt
> definiert

Kannst du übrigens auch warnen lassen (-Wundef) und per -Werror als 
Fehler behandeln lassen.

von A. S. (Gast)


Lesenswert?

Walter T. schrieb:
> Danke, die Erklärung klingt so, dass die Chancen sehr gut sind, dass ich
> sie mir merken kann.

Oder Warnungen einschalten, dann sagt er, dass beide nicht definiert 
sind.

Das wäre aber so eine Warnung, die ich je nach Architektur der SW auch 
ausschalte.

von Walter T. (nicolas)


Lesenswert?

Danke für die Tipps mit der Warning. Mal wieder etwas, was in -Wpedantic 
nicht drin ist.

: Bearbeitet durch User
von BeBe (Gast)


Lesenswert?

Die meisten #if Konstrukte können inzwischen mit normalen if gelöst 
werden. Die Compiler sind inzwischen sehr gut. Ich nutze #if Konstrukte 
eigentlich nur für Plattformschalter und dann meistens auch nur 
außerhalb von Funktionen.
Falls man sich auf den -Wpedantic Schalter nicht verlassen will (ich 
kenne meine Kollegen) kann man wichtige Proprozessor Werte auch direkt 
prüfen.
1
#if !defined(M51TBL) || !defined(SERIAL) || !defined(MODEL)
2
#error "Important compiler switches are undefined! "
3
#endif

Beitrag #6699280 wurde von einem Moderator gelöscht.
von A. S. (Gast)


Lesenswert?

BeBe schrieb:
> #if !defined(M51TBL) || !defined(SERIAL) || !defined(MODEL)
> #error "Important compiler switches are undefined! "
> #endif

Geht das auch rekursiv? In diesem Fall ist es ja so, dass MYCHOICE 
definiert war.

von Stefan F. (Gast)


Lesenswert?

A. S. schrieb:
>> #if !defined(M51TBL) || !defined(SERIAL) || !defined(MODEL)
>> #error "Important compiler switches are undefined! "
>> #endif

> Geht das auch rekursiv? In diesem Fall ist es ja so, dass MYCHOICE
> definiert war.

Ich kann die Frage nicht beantworten, aber ich kann von obigem Konstrukt 
nur abraten. Denn die Fehlermeldung sagt nicht, welcher "switch" fehlt. 
Lieber ein bisschen weniger faul sein, und drei individuelle 
Fehlermeldungen programmieren. Man schießt sich sonst nur unnötig ins 
eigene Knie.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Preprocessor- und enum-Konstanten zu mischen ist keine gute Idee.

Wenn man das at-compile-time abfangen will, wäre dies eine Lösung:
1
#define choice_a    0
2
#define choice_b    1

Dann kann der Rest des Codes so bleiben.

Okay, man sollte vielleicht choice_a und choice_b noch groß schreiben, 
um auszudrücken, dass es sich um Preprocessor-Konstanten handelt.

: Bearbeitet durch Moderator
von BeBe (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ich kann die Frage nicht beantworten, aber ich kann von obigem Konstrukt
> nur abraten. Denn die Fehlermeldung sagt nicht, welcher "switch" fehlt.
> Lieber ein bisschen weniger faul sein, und drei individuelle
> Fehlermeldungen programmieren. Man schießt sich sonst nur unnötig ins
> eigene Knie.

Du hast natürlich Recht, Einzelfehlermeldungen sind DAU Idiotensicher.
Zusammengefasst sehe ich aber im Beispiel dass einer von 3 nicht da ist.
Das ist dann nur noch Programmieranfänger-Unsicher.

Ins Knie schiesse ich mir damit nicht, das ist in einer normalen IDE 
schnell entdeckt.

von A. S. (Gast)


Lesenswert?

Frank M. schrieb:
> #define choice_a    0

Wobei wir generell 0 als Auswahlwert vermeiden. Eben weil jede falsche 
Schreibweise zu 0 führt. 0 ist quasi nur literal als ausschalten 
erlaubt, bzw. hier "No choice".

von Stefan F. (Gast)


Lesenswert?

BeBe schrieb:
> Ins Knie schiesse ich mir damit nicht, das ist in einer normalen IDE
> schnell entdeckt.

Das denke ich auch oft. Und 5 Jahre später sitze ich dann davor und 
frage mich "Welcher Vollidiot hat das gemacht?". Ich kann mir dann nicht 
einmal vorstellen, dass ich das selbst war.

von A. S. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ich kann die Frage nicht beantworten

War auch nur rhetorisch.

Wie Frank schon schrieb, gehören enums nicht in #if.

Und besser auf 0 abfragen, dann ist !defined mit drin. 0 dann natürlich 
als Merkmal reservieren.


#if !M51TBL || !SERIAL || !MODEL

von BeBe (Gast)


Lesenswert?

A. S. schrieb:
> Geht das auch rekursiv? In diesem Fall ist es ja so, dass MYCHOICE definiert 
war.
Regel: Du solltest Präprozessor  ifs nur für Compilerschalter nutzen und 
nicht mit enums vermischen.
Präprozessor  Anwendung nur für verschiedene Plattformen/ Ausprägungen:

#if defined(EMBEDDED_OS)…

Wenn du außerhalb von Funktionen Bedingungen brauchst, geht halt nur #if 
und dann machen enums keinen Sinn)
In deinem Beispiel brauchst Du aber kein Präprozessor if. Das erledigt 
das normale if auch.
Ich hab mal Deinen Code angepasst.
1
#include<stdio.h>
2
#include <assert.h>
3
4
enum choice_e {choice_a,choice_b};
5
6
enum config_e {my_choise = choice_b};
7
//enum config_e {my_choise = 68768};
8
9
// Check: statt "#if defined " korrekte Einstellung mit Static_assert prüfen (ab C11) 
10
_Static_assert (my_choise==choice_a || my_choise==choice_b , "Falsche Konfiguration");
11
12
13
14
char runToShow(void)
15
{
16
    volatile char a;
17
    if( my_choise == choice_a )
18
    {
19
        a = 'a';
20
    }
21
    else if ( my_choise == choice_b )
22
    {
23
        a = 'b';
24
    }
25
    else
26
    {
27
        // Fehlermeldung provozieren
28
        assert(0); // 1. runtime assertion, oder
29
        error_else_path_not_allowed(); // 2. Linkerfehler durch unbekannte Funktion
30
    }
31
    return a;
32
}
33
34
int main()
35
{
36
    char ret;
37
    ret = runToShow();
38
    printf("%c", ret);
39
}

So meckert der Compiler wenn my_choise ,choice_a… nicht definiert sind. 
(expected expression before '==' token)
Wenn man eine Felermeldung im else Zweig braucht, gibt es zwei 
Möglichkeiten in C:
1.  Runtime assert erzeugen
2.  oder einen Linkerfehler provozieren.

(In C++ könnte man ggf im else Zweig ggf per templates einen 
Compilerfehler erzeugen.)

Nicht perfekt und holprig. Ich will Präprozessor ifs nicht verteufeln, 
man braucht sie immer mal wieder, aber man sollte die Alternativen 
kennen. (Ich baue meine Sachen inzwischen in C++, dort ist der Umgang 
mit Konstanten deutlich besser).

von Stefan F. (Gast)


Lesenswert?

Das stimmt, manchen Leuten ist nicht klar, dass der Compiler toten Code 
sowieso weg optimiert. Es geht sogar noch weiter:
1
#define debug_enabled false
2
3
void debug_print(char* message)
4
{
5
    if (debug_enabled)
6
    { 
7
       ... // Meldung irgendwie ausgeben
8
    }
9
}
10
11
int main()
12
{
13
    debug_print("main gestartet");
14
    ....
15
    debug_print("main beendet");
16
}

Hier wird nicht nur die Ausgabe weg gelassen, sondern die ganze Funktion 
debug_print() und auch die Strings werden weg optimiert.

Dieser Hokuspokus mit komplexen #ifdef Makros ist nur selten nötig. 
Stattdessen riskiert man, den Quelltext schlecht lesbar zu machen und 
die Makros falsch zu benutzen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Stefan ⛄ F. schrieb:
> Hier wird nicht nur die Ausgabe weg gelassen, sondern die ganze Funktion
> debug_print() und auch die Strings werden weg optimiert.

Bei Verteilung auf mehrere Quelldateien allerdings nur, wenn man -flto 
benutzt. Der Compiler weiß ja ansonsten in Datei A nicht, dass die 
aufgerufene Funktion, deren Implementierung sich in Datei B befindet, 
eigentlich gar nichts tut.

von Stefan F. (Gast)


Lesenswert?

Jörg W. schrieb:
> Bei Verteilung auf mehrere Quelldateien allerdings nur, wenn man -flto
> benutzt.

Ja guter Hinweis

von BeBe (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Hier wird nicht nur die Ausgabe weg gelassen, sondern die ganze Funktion
> debug_print() und auch die Strings werden weg optimiert.

Volle Zustimmung. Viele glauben, dass der Compiler im letzten 
Jahrtausend stehen blieb. ("C11 - was ist das?")

Wobei Lustigerweise Debug Funktionen wie debug_print() die einzigen 
Funktionen sind, die ich per Präprozessor wrappe.
Aber eher wegen _LINE_ Macro als wegen Optimierungen... ;-)

von Mark B. (markbrandis)


Lesenswert?

Walter T. schrieb:
> Ich nehme mal an, dass hier jeder weiss, welchen Wert die Funktion
> zurückgibt. Aber warum gibt sie diesen Wert zurück?

Die viel interessante Frage ist doch:
Warum um alles in der Welt sollte man solchen Code schreiben wollen?

von Walter T. (nicolas)


Lesenswert?

My goals are beyond your understanding.

von Rolf M. (rmagnus)


Lesenswert?

Walter T. schrieb:
> My goals are beyond your understanding.

Wenn andere nicht verstehen, was du tust, gibt es dafür zwei mögliche 
Ursachen ;-)

von Walter T. (nicolas)


Lesenswert?

Rolf M. schrieb:
> Wenn andere nicht verstehen, was du tust, gibt es dafür zwei mögliche
> Ursachen ;-)

Mir fallen noch mehr ein. In diesem Fall ist sie banal: Die Frage ist so 
formuliert, dass der Eindruck entsteht, dass keine echte Antwort 
erwartet wird.

Falls es trotzdem jemanden interessieren sollte: 
Beitrag "STM32F446RE - Timer 4 PB7 geht nicht"

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.