Bitmasks mit defines machen Probleme



  • Hallo,

    ich hab ein Problem welches ich momentan nicht nachvollziehen kann. Folgender Code macht nicht was er soll:

    if ((MY_BIT_MASK | state) == MY_BIT_MASK)
        std::cout << "valid!\n";
    else
        std::cout << "invalid!\n";
    

    Hier wird valid ausgegeben, obwohl MY_BIT_MASK = 56 und state = 64 ist. Wenn ich das Ganze explizit hinschreibe, also so:

    if ((56 | 64) == 56)
        std::cout << "valid!\n";
    else
        std::cout << "invalid!\n";
    

    dann funktioniert es auf einmal und es wird invalid ausgegeben - obwohl das doch genau das gleiche sein sollte wie der obige Code. Die Werte von MY_BIT_MASK und state hab ich mir schon ausgeben lassen und diese sind genau 56 und 64...

    Was läuft hier schief?



  • Wie ist MY_BIT_MASK definiert?



  • Folgendermaßen:

    #define MASK_FLAG_4	0x08
    #define MASK_FLAG_5	0x10
    #define MASK_FLAG_6	0x20
    
    #define MY_BIT_MASK	MASK_FLAG_4 | MASK_FLAG_5 | MASK_FLAG_6
    

    Die Variable "state" hab ich zu testzwecken manuell auf 64 gesetzt, also folgendermaßen:

    int state = 0x40;
    


  • Nachdem der Präprozessor durch ist, sieht das dann wie folgt aus:

    if ((0x08 | 0x10 | 0x20 | state) == 0x08 | 0x10 | 0x20)
    

    Merkste was?



  • Da fehlen Klammern:

    #define MY_BIT_MASK (MASK_FLAG_4 | MASK_FLAG_5 | MASK_FLAG_6)
    

    http://de.cppreference.com/w/cpp/language/operator_precedence



  • Oh, tatsächlich jetzt funktioniert es.

    Aber ich dachte der kann das gleich auswerten? Also die flags sind ja allesamt Konstanten, kann man dem Präprozessor nicht sagen dass er das gleich zu einer fertigen Zahl evaluieren soll?

    Also ich könnte es ja auch manuell hinschreiben, etwa so:

    #define MY_BIT_MASK 56
    

    aber dann liest es sich halt nicht mehr so schön, da sich dann jeder fragen wird woher jetzt die 56 kommt...



  • Der Präprozessor macht Textersetzung, mehr nicht.

    edit: Außerdem, wenn er irgendwas auswerten würde, dann sicher nicht so, dass sich dadurch die Bedeutung des Programms verändert!



  • Warum benutzt du Makros statt Konstanten?



  • manni66 schrieb:

    Warum benutzt du Makros statt Konstanten?

    +1
    Wie gesagt: der Präprozessor macht lediglich Textersetzung.
    Du kannst auch schreiben:

    #define FOO dasistkeingültigerC++Programmcode,aber egal, der Präprozessor fügt das schön für mich einfach ein
    

    Wenn du eine Konstante hast, wie:

    const /* constexpr wenn C++11 verfügbar */ int bit_mask = a | b | c;
    

    Dann wird a | b | c ausgerechnet und in bit_mask gespeichert. Wenn a | b | c ein konstanter Ausdruck ist, wird im Endeffekt auch so etwas wie Textersetzung gemacht, nur korrekt. Da steht dann das Ergebnis von a | b | c, nicht die Operation selber.



  • Nathan schrieb:

    const /* constexpr wenn C++11 verfügbar */ int bit_mask = a | b | c;
    

    Welchen Vorteil bringt da constexpr ?



  • Grund #1 wieso Makros böse sind. Man verwendet keine Makros, ganz besonders nicht für Konstanten...



  • c++11fanatiker schrieb:

    Nathan schrieb:

    const /* constexpr wenn C++11 verfügbar */ int bit_mask = a | b | c;
    

    Welchen Vorteil bringt da constexpr ?

    Keinen richtigen. Wenn das rechts der Zuweisung ein konstaner Ausdruck ist, ist bit_mask auch ein konstanter Ausdruck. Wenn allerdings aus irgendwelchen Gründen das rechts der Zuweisung kein konstanter Ausdruck ist, gibt es mit constexpr direkt einen Fehler.
    Außerdem dokumentiert das besser.



  • Nathan schrieb:

    Wenn allerdings aus irgendwelchen Gründen das rechts der Zuweisung kein konstanter Ausdruck ist, gibt es mit constexpr direkt einen Fehler.

    Überzeugt mich nicht. 1. Wieso sollte ich Konstanten erst später deklarieren? 2. Der Compiler kann das soweiso optimieren, falls die Konstante nicht in constexpr-Ausdrücken gebraucht wird, erhalte ich auch keine Fehlermeldung. Und falls doch, dann habe ich eine, aber die hätte ich mit constexpr auch.



  • Nathan schrieb:

    Wenn du eine Konstante hast, wie:

    const /* constexpr wenn C++11 verfügbar */ int bit_mask = a | b | c;
    

    Dann wird a | b | c ausgerechnet und in bit_mask gespeichert. Wenn a | b | c ein konstanter Ausdruck ist, wird im Endeffekt auch so etwas wie Textersetzung gemacht, nur korrekt. Da steht dann das Ergebnis von a | b | c, nicht die Operation selber.

    Aber ist das nicht rechentechnisch aufwendiger als mit einem Makro? Also Konstanten sind ja immernoch Variablen (sonst könnte man sie ja nicht per const_cast doch noch verändern) während ich mit Makros wirklich nur Werte da stehen habe?



  • happystudent schrieb:

    Aber ist das nicht rechentechnisch aufwendiger als mit einem Makro?

    Schau dir an, was dein Compiler draus macht und staune... 😉



  • c++11fanatiker schrieb:

    Nathan schrieb:

    Wenn allerdings aus irgendwelchen Gründen das rechts der Zuweisung kein konstanter Ausdruck ist, gibt es mit constexpr direkt einen Fehler.

    Überzeugt mich nicht. 1. Wieso sollte ich Konstanten erst später deklarieren? 2. Der Compiler kann das soweiso optimieren, falls die Konstante nicht in constexpr-Ausdrücken gebraucht wird, erhalte ich auch keine Fehlermeldung. Und falls doch, dann habe ich eine, aber die hätte ich mit constexpr auch.

    1. Wieso sollte man einer Funktion, die einen non-nullptr erwartet einen nullptr übergeben?
    2. const sagt: Das ist eine Konstante, die darfste nicht verändern.
    constexpr sagt: Das ist ein konstanter Ausdruck, keine Variable mehr. Das wird später evtl. einfach hardgecoded in den Programmcode und belegt keinen Speicher (Antwort auch @happystudent).
    const kann das auch sagen, aber ohne Wissen über - um bei meinem Beispiel zu bleiben - was a, b und c sind, weiß man nicht, ob das ein konstanter Ausdruck ist.
    Die beste Dokumentation ist die, die man direkt dem Code entnehmen kann.

    Edit @happystudent:
    Ein const_cast auf eine Variable, die ursprünglich mit const deklariert wurde, ist UB. Du kannst nur Refernenzen o.ä. auf const zu Referenzen auf nicht-const casten, und das auch nur, wenn das übergeben Objekt wirklich nicht konstant ist, da der Compiler die constexpr-Optimierung schon gemacht haben könnte, und die Variable keine Adresse mehr hat.



  • happystudent schrieb:

    Also Konstanten sind ja immernoch Variablen (sonst könnte man sie ja nicht per const_cast doch noch verändern) während ich mit Makros wirklich nur Werte da stehen habe?

    Nö. Der const_cast geht doch nur, wenn die Variable in Wirklichkeit eine variable Variable ist. Tust Du sie schon als const erzeugen, kann der Compiler sie z.B. ins ROM schreiben oder wasauchimmer machen, was man mit konstanden Variablen so macht, üblicherweise den Speicher wegoptimieren.



  • Ok, danke euch alle, wieder einiges gelernt 👍

    Btw:

    Nathan schrieb:

    Ein const_cast auf eine Variable, die ursprünglich mit const deklariert wurde, ist UB.

    Was bedeutet "UB"?



  • UB steht für Undefined Behaviour.
    Das komplette Programmverhalten ist somit nicht mehr im Standard definiert.
    Um das Standardbeispiel aufzugreifen: Der Compiler könnte an der Stelle Code generieren, der deine Festplatte formatiert und wäre komplett standardkonform.
    In der Regel wird das Programm aber einfach crashen.



  • Dazu noch unbedingt zu sagen:
    Der Compiler dürfte bei UB auch bewirken, daß Dämonen aus deiner Nase fliegen!
    http://www.catb.org/jargon/html/N/nasal-demons.html

    Haha, Rätselaufgabe:

    int souls=5;
    int deamons=++souls+souls++;
    cout<<deamons;
    

    Wieviele Dämonen?


Log in to reply