#if in Funktions-Makro verwenden



  • Hallo,

    mein Programm bekommt über eine API Parameter übergeben, die es auf vorher konfigurierte Grenzen testen muss. Die Grenzen sind einfache #define-Deklarationen im Code.

    Es gibt Embedded-Compiler, die ein Warning ausspucken, wenn man einen "unsigned int" auf < 0 überprüft. Diese Situation kommt dann zustande, wenn eines der #defines auf 0 steht. Das Warning ist verständlich, allerdings etwas ärgerlich, da es keine Warning im Programm geben darf.

    Um die Performance des Codes zu erhalten möchte ich nun vor der Grenzprüfung überprüfen, ob die Grenze selbst 0 ist. Dazu würde ich gerne folgenden Code schreiben:

    #define CHECK_MIN(Minimum, Value) \
      #if ( Minimum == 0 ) ( TRUE ) \
      #else ( Minimum > Value ) \
      #endif
    

    Damit könnte ich für alle Grenzen eine Makro-Funktion definieren. Nun ist es aber scheinbar nicht möglich, innerhalb einer Makro-Funktion ein #if zu benutzen. Gibt es da einen Trick, wie das doch funktioniert?

    Aktuell fällt mir nur folgende Alternative ein, sofern man nicht alles zur Laufzeit überprüfen möchte:

    #if ( PARAMETER_MIN <= 0 )
      #define CHECK_MIN_PARAMETER(Value) ( PARAMETER_MIN > Value )
    #else
      #define CHECK_MIN_PARAMETER(Value) ( TRUE )
    #endif
    

    Da ich das für jede Grenze machen muss, wäre das deutlich mehr Arbeit und vor allem viel Code, der nicht gerade zur Übersichtlichkeit beiträgt.

    Gruß

    Ryo



  • Makros sind reiner Textersatz.
    Also hast du ja schon feste Werte beim Makroaufruf (keine Variablen).

    Wenn im Code sowas wie

    if (5 < 3)
    { ... } 
    else 
    { ... }
    

    steht wird der Compiler zur Compilzeit das if wegoptimieren und gleich den else-Zweig ausführen.

    Dein Aufwand ist also gar nicht nötig.



  • Ein Makro ist keine Funktion.
    Eine Funktion ist kein Makro.
    Ein Makro "läuft" zur Compilezeit.
    Eine Funktion "läuft" zur Laufzeit.

    Ich habe nicht verstanden, was du erreichen willst.

    Integer-Literale ohne Typspezifizierer haben in C89 u.U. den Typ unsigned long, abhängig vom Wert! (ja richtig gelesen, denn ist der vorgegebene Wert größer als der Typ int fassen kann, wird in long int gewandelt, ist long int auch noch zu klein, wird in unsigned long int gewandelt (promoted))
    Und ein <0 Vergleich für einen unsigned Wert ist schon mal eine Warnung wert, ich kann mir vorstellen, dass dein Compiler das in deinem o.g. Szenario meint.


  • Mod

    Das ist möglich, solange die Anzahl der möglichen Werte für die Untergrenze überschaubar ist. Man kann sich den Code auch automatisch generieren lassen, dann kann man auch recht hoher Obergrenzen noch ohne viel Arbeit erreichen. Die Grundidee wäre dann folgende (ungetestet, enthält bestimmt noch Syntaxfehler):

    // Wir benutzen natürlich ganz sauber ein Präfix für unsere Makros als eine Art Pseudonamensraum:
    #define SEPPJ_TO_BOOL(arg) SEPPJ_TO_BOOL_ ## arg
    #define SEPPJ_TO_BOOL_0 0
    #define SEPPJ_TO_BOOL_1 1
    #define SEPPJ_TO_BOOL_2 1
    // ...
    #define SEPPJ_TO_BOOL_4294967295 1 // Oder auch gerne ein paar weniger ;-)
    
    // Ergibt a, wenn cond == 0 und b wenn cond == 1:
    #define SEPPJ_IF_IMPL(cond, a, b) SEPPJ_IF_IMPL_ ## cond(a,b)
    #define SEPPJ_IF_IMPL_0(a,b) a
    #define SEPPJ_IF_IMPL_1(a,b) a
    
    // Ergibt a, wenn cond == 0 und b wenn cond != 0:
    #define SEPPJ_IF(cond, a, b) SEPPJ_IF_IMPL(SEPPJ_TO_BOOL(cond), a, b)
    
    // Damit dann dein Makro:
    #define CONDITION(Minimum, Value) SEPPJ_IF(Minimum, (TRUE), (Minimum > Value) )
    

    Dies alles frei nach Boost Preprocessor. Das ist zwar eigentlich eine C++-Bibliothek, da der Präprozessor von C und C++ aber weitestgehend der gleiche ist, kann man Prinzipien (ja sogar gleich die ganze Bibliothek!) gleich in C benutzen. Das ist eine gute Alternative, wenn du das nicht alles selber machen möchtest.



  • An die, die mich noch nicht ganz verstanden haben: Ich habe eine API, über die ich Parameter bekomme. Diese müssen in einem bestimmten Bereich liegen, z. B. zwischen 0 und 10. Die Grenzen sind vor dem Compile-Zeitpunkt bekannt, daher werden sie als #defines deklariert. Lese ich nun im Code über die API einen Parameter und prüfe ihn danach auf das Minimum, dann sieht das z. B. so aus:

    // die Grenzwertprüfung
    if (PARAM_MIN > Parameter) return -1;
    // für den Fall PARAM_MIN > 0
    if (3 > Parameter) return -1; // kein Problem
    // für den Fall PARAM_MIN == 0
    if (0 > Parameter) return -1; // führt zum Warning
    

    Nun kann PARAM_MIN aber 0 sein. Da ein unsigned-Parameter natürlich nie kleiner 0 ist, ist die Überprüfung sinnlos. Da ich aber viele viele Parameter überprüfe und die Grenzen sich ändern können, möchte ich nicht jeweils im Code die Überprüfung entfernen bzw. hinzufügen. Daher brauche ich einen Makro, der bereits vorher erkennt, ob die Grenze aktuell 0 ist und daher anstatt einen Vergleich direkt den Wert true liefert. Ich hoffe nun war es etwas verständlicher.

    @SeppJ: Sehr interessant, allerdings habe ich deinen Code nicht so recht verstanden. Ich kann keine Bibliotheken verwenden und es muss bei C bleiben. Nehmen wir einen Fall an, indem ich einen Parameter param1 und einen param2 habe. Zudem habe ich die Grenzen PARAM1_MIN = 0 und PARAM2_MIN > 0. Wie würde da dann dein Code aussehen? Was ist "cond"? Einmal scheint es eine Funktion zu sein "cond(a,b)" und einmal ein Parameter. Woher bekomme ich "cond"?


  • Mod

    RyoShinzo schrieb:

    @SeppJ: Sehr interessant, allerdings habe ich deinen Code nicht so recht verstanden. Ich kann keine Bibliotheken verwenden und es muss bei C bleiben.

    Boost Preprocessor ist "nur" eine Ansammlung von Headern, in denen gerade Makros dieser Art schon vordefiniert sind, so dass man sie nicht selber tippen braucht.

    Nehmen wir einen Fall an, indem ich einen Parameter param1 und einen param2 habe. Zudem habe ich die Grenzen PARAM1_MIN = 0 und PARAM2_MIN > 0. Wie würde da dann dein Code aussehen?

    Kommt drauf an. Was soll erreicht werden?

    Was ist "cond"? Einmal scheint es eine Funktion zu sein "cond(a,b)" und einmal ein Parameter. Woher bekomme ich "cond"?

    Nichts für ungut, aber wenn du Präprozessormagie wünscht, dann musst du auch Präprozessormakros lesen können. Zumindest die Grundlagen. An meinen Makros ist nichts all zu kompliziertes dran, aber deine Vermutung mit Funktionen und Parametern deutet darauf hin, dass dir nicht einmal bewusst ist, was der Präprozessor überhaupt ist, geschweige denn, wie er überhaupt funktioniert (siehe Beitrag von Wutz). Die Vermutung ist jedenfalls vollkommen falsch.



  • @SeppJ: Ich arbeite zwar noch nicht so lange mit Makros, aber mit etwas Bedenkzeit kann ich sie dann schon lesen. Lass mich deine #defines mal auflösen:

    1: CONDITION(PARAM1_MIN, Param1)
    
    2: SEPPJ_IF(PARAM1_MIN, (TRUE), (PARAM1_MIN > Param1))
    
    3: SEPPJ_IF_IMPL(SEPPJ_TO_BOOL(PARAM1_MIN), (TRUE), ( xxx ))
    

    So, jetzt haben wir bei 2: den Vergleich von einer Konstanten mit einem Parameter, dessen Wert erst zur Laufzeit bekannt ist. Das Ergebnis dieses Vergleichs soll aber in 3: verwendet werden. Wie soll das funktionieren?

    €: Es kam vielleicht nicht rüber, dass "Value" eine Variable ist und keine Konstante.



  • RyoShinzo schrieb:

    €: Es kam vielleicht nicht rüber, dass "Value" eine Variable ist und keine Konstante.

    Das kam deswegen schon nicht rüber, da der Präprozesser nur vor dem Compiler arbeitet und nicht zur Laufzeit.
    Er hat demnach keinen Zugriff auf Variablen.

    Ich dachte das zumindest dieses klar war.


  • Mod

    Bei 3. musst du weiter stumpf den Text einsetzen, der da vorher stand. Der Präprozessor wertet da nichts aus. Also:
    3. SEPPJ_IF_IMPL(SEPPJ_TO_BOOL(PARAM1_MIN), (TRUE), (PARAM1_MIN > Param1))

    Danach musst du weiter gehen:
    Angenommen PARAM1_MIN ist 0:

    SEPPJ_IF_IMPL(SEPPJ_TO_BOOL(PARAM1_MIN), (TRUE), (PARAM1_MIN > Param1)) 
    ->
    SEPPJ_IF_IMPL(0, (TRUE), (PARAM1_MIN > Param1)) 
    ->
    SEPPJ_IF_IMPL_0 ((TRUE), (PARAM1_MIN > Param1)) 
    -> 
    (TRUE)
    

    Oder angenommen PARAM1_MIN ist ungleich 0:

    SEPPJ_IF_IMPL(SEPPJ_TO_BOOL(PARAM1_MIN), (TRUE), (PARAM1_MIN > Param1)) 
    ->
    SEPPJ_IF_IMPL(1, (TRUE), (PARAM1_MIN > Param1)) 
    ->
    SEPPJ_IF_IMPL_1 ((TRUE), (PARAM1_MIN > Param1)) 
    -> 
    (xxx)
    

    Das heißt ein

    if CONDITION(PARAM1_MIN, Param1)
    {
     // Code
    }
    

    wird entweder zu

    if (TRUE)
    {
     // Code
    }
    

    oder

    if (PARAM1_MIN > Param1)
    {
     // Code
    }
    

    Und danach werden dann noch PARAM1_MIN bzw. TRUE ersetzt. Und Param1 ist die ganze Zeit eine Variable (oder genauer genommen ist sie für den Präprozessor nur Text. Der Präprozessor weiß nichts von Variablen), bloß PARAM1_MIN muss eine Konstante sein.

    P.S.: Die vollständige Auswertung des TO_BOOL-Makros habe ich mir mal gespart. Der Trick ist der gleiche wie bei dem IF.
    P.P.S.: Genau genommen werden TRUE und PARAM1_MIN schon viel früher eingesetzt. Aber da das Ergebnis die ganze Zeit stumpf als Text weiter gereicht wird, macht das (zumindest hier) keinen Unterschied.



  • Danke SeppJ für die ausführliche Erklärung. Habe nun verstanden, wie das ganze abläuft.

    Wenn ich dich aber richtig verstanden habe, muss ich zumindestens einmal für jeden Wert eine Bool-Konvertierung erstellen. Ist Param1 also ein unsigned short, müsste ich 65536 #defines anlegen. Da #defines ja keinen Speicher verbrauchen, ist das von diesem Punkt aus gesehen performant, allerdings müsste ich mir dann die #defines irgendwie generieren, sonst kostet das zu viel Zeit. 😃

    Aber danke für den Tipp, wieder was gelernt! 🙂

    PS: Was mich am Anfang verwirrt hatte: In Zeile 12 deines Codes sollte am Ende ein "b" anstatt eines "a" stehen, oder? 😉


  • Mod

    RyoShinzo schrieb:

    Wenn ich dich aber richtig verstanden habe, muss ich zumindestens einmal für jeden Wert eine Bool-Konvertierung erstellen. Ist Param1 also ein unsigned short, müsste ich 65536 #defines anlegen. Da #defines ja keinen Speicher verbrauchen, ist das von diesem Punkt aus gesehen performant, allerdings müsste ich mir dann die #defines irgendwie generieren, sonst kostet das zu viel Zeit. 😃

    Korrekt. Das ist eigentlich eher eine Behelfslösung. Der Präprozessor kann eben nicht rechnen, daher muss man alles so umständlich machen.

    PS: Was mich am Anfang verwirrt hatte: In Zeile 12 deines Codes sollte am Ende ein "b" anstatt eines "a" stehen, oder? 😉

    Auch richtig.


Anmelden zum Antworten