Hallo zusammen,
ich bin gestern auf ein Problem gestoßen, dessen ich mir vorher nicht
bewusst war. Das Programm sah sinngemäß so aus:
1
enumchoice_e
2
{
3
choice_a,
4
choice_b,
5
};
6
7
#define MYCHOICE choice_b
8
9
10
/* Funktion zum Testen von Sachen. */
11
charrunToShow(void)
12
{
13
volatilechara;
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
volatilecharb='c';
27
28
returna;
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?
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?
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…
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.
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.
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.
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.
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.
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.
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.
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.
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".
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.
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
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
enumchoice_e{choice_a,choice_b};
5
6
enumconfig_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)
error_else_path_not_allowed();// 2. Linkerfehler durch unbekannte Funktion
30
}
31
returna;
32
}
33
34
intmain()
35
{
36
charret;
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).
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
voiddebug_print(char*message)
4
{
5
if(debug_enabled)
6
{
7
...// Meldung irgendwie ausgeben
8
}
9
}
10
11
intmain()
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.
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.
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... ;-)
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?
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"