#define auf enum

OP #6699208
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?
OP #6699213
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?
#6699219
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…
Gast #6699273
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.
Gast #6699292
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.
Moderator Persönliche Seite #6699297
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.
Gast #6699414
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.
Gast #6699424
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.
Gast #6699565
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).
Gast #6699589
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.
Moderator Persönliche Seite #6699593
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.
Gast #6699783
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... ;-)

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren