Ein bisschen mit einem Booleschen vergleichen

12

Angenommen, ich habe eine Reihe von Flags, die in einem uint16_t codiert sind flags. Zum Beispiel AMAZING_FLAG = 0x02. Jetzt habe ich eine Funktion. Diese Funktion muss prüfen, ob ich das Flag ändern möchte, denn wenn ich das tun möchte, muss ich in Flash schreiben. Und das ist teuer. Daher möchte ich eine Überprüfung, die mir sagt, ob flags & AMAZING_FLAGgleich ist doSet. Dies ist die erste Idee:

setAmazingFlag(bool doSet)
{
    if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0)) {
        // Really expensive thing
        // Update flags
    }
}

Dies ist keine intuitive if-Anweisung. Ich denke, es sollte einen besseren Weg geben, so etwas wie:

if ((flags & AMAZING_FLAG) != doSet){

}

Aber das funktioniert eigentlich nicht, truescheint gleich zu sein 0x01.

Gibt es eine gute Möglichkeit, ein bisschen mit einem Booleschen Wert zu vergleichen?

Cheiron
quelle
2
Es ist nicht ganz klar, was Ihre Logik sein muss. Ist es das : (flags & AMAZING_FLAG) && doSet?
Kaylum
Die Frage ist nicht klar. Wir wollen überprüfen, ob die 'Flags' 0x01 sind oder nicht. Willst du das Wenn ja, können wir den bitweisen Operator '&' verwenden.
Vikas Vijayan
Wenn Sie es lesbarer machen möchten, rufen Sie setAmazingFlag nur auf, wenn doSet true ist, dann wird der Funktionsname besser ausgecheckt, andernfalls haben Sie eine Funktion, die möglicherweise das tut, was der Name sagt, was zu schlechtem
Codelesen führt
Wenn Sie nur versuchen, es besser lesbar zu machen, erstellen Sie einfach eine Funktion "flagsNotEqual".
Jeroen3

Antworten:

18

Um eine Zahl ungleich Null in 1 (wahr) umzuwandeln, gibt es einen alten Trick: Wenden Sie den !Operator (nicht) zweimal an.

if (!!(flags & AMAZING_FLAG) != doSet){
user253751
quelle
4
Oder (bool)(flags & AMAZING_FLAG) != doSet- was ich direkter finde. ( Dies deutet jedoch darauf hin, dass Herr Microsoft ein Problem damit hat.) Auch ((flags & AMAZING_FLAG) != 0)ist es wahrscheinlich genau das, worauf es kompiliert und ist absolut explizit.
Chris Hall
"Auch ((flags & AMAZING_FLAG)! = 0) ist wahrscheinlich genau das, worauf es kompiliert und ist absolut explizit". Würde es? Was ist, wenn doSet wahr ist?
Cheiron
@ChrisHall Ergibt (bool) 2 1 oder ist es immer noch 2 in C?
user253751
3
C11 Standard, 6.3.1.2 Boolescher Typ: "Wenn ein Skalarwert in _Bool konvertiert wird, ist das Ergebnis 0, wenn der Wert gleich 0 ist; andernfalls ist das Ergebnis 1.". Und 6.5.4 Cast-Operatoren: "Vor einem Ausdruck mit einem in Klammern gesetzten Typnamen wird der Wert des Ausdrucks in den benannten Typ konvertiert."
Chris Hall
@Cheiron, um es klarer zu machen: ((bool)(flags & AMAZING_FLAG) != doSet)hat den gleichen Effekt wie (((flags & AMAZING_FLAG) != 0) != doSet)und beide werden wahrscheinlich genau das gleiche kompilieren. Der Vorschlag (!!(flags & AMAZING_FLAG) != doSet)ist äquivalent, und ich kann mir vorstellen, dass er auch zu derselben Sache kompiliert wird. Es ist ganz und gar eine Geschmackssache, die Sie für klarer halten: Bei der (bool)Besetzung müssen Sie sich daran erinnern, wie Besetzungen und Konvertierungen in _Bool funktionieren. Das !!erfordert eine andere mentale Gymnastik oder damit Sie den "Trick" kennen.
Chris Hall
2

Sie müssen die Bitmaske in eine boolesche Anweisung konvertieren, die in C den Werten 0oder entspricht 1.

  • (flags & AMAZING_FLAG) != 0. Der häufigste Weg.

  • !!(flags & AMAZING_FLAG). Etwas üblich, auch in Ordnung, aber etwas kryptisch.

  • (bool)(flags & AMAZING_FLAG). Moderner C-Weg ab C99 und darüber hinaus.

Nehmen Sie eine der oben genannten Alternativen und vergleichen Sie sie mit Ihrem Booleschen Wert unter Verwendung von !=oder ==.

Lundin
quelle
1

Aus logischer Sicht flags & AMAZING_FLAGist nur eine Bitoperation, die alle anderen Flags maskiert. Das Ergebnis ist ein numerischer Wert.

Um einen booleschen Wert zu erhalten, würden Sie einen Vergleich verwenden

(flags & AMAZING_FLAG) == AMAZING_FLAG

und kann nun diesen logischen Wert mit vergleichen doSet.

if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)

In C kann es aufgrund der impliziten Konvertierungsregeln von Zahlen in boolesche Werte Abkürzungen geben. Du könntest also auch schreiben

if (!(flags & AMAZING_FLAG) == doSet)

um das knapper zu schreiben. Die frühere Version ist jedoch in Bezug auf die Lesbarkeit besser.

Ctx
quelle
1

Sie können eine Maske basierend auf dem doSetWert erstellen :

#define AMAZING_FLAG_IDX 1
#define AMAZING_FLAG (1u << AMAZING_FLAG_IDX)
...

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Jetzt kann Ihr Scheck folgendermaßen aussehen:

setAmazingFlag(bool doSet)
{
    const uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

    if (flags & set_mask) {
        // Really expensive thing
        // Update flags
    }
}

Auf einigen Architekturen !!kann zu einem Zweig kompiliert werden, und auf diese Weise können Sie zwei Zweige haben:

  1. Normalisierung durch !!(expr)
  2. Vergleichen mit doSet

Der Vorteil meines Vorschlags ist eine garantierte Einzelniederlassung.

Hinweis: Stellen Sie sicher, dass Sie kein undefiniertes Verhalten einführen, indem Sie um mehr als 30 nach links verschieben (vorausgesetzt, die Ganzzahl beträgt 32 Bit). Dies kann leicht erreicht werden durch astatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");

Alex Lop.
quelle
1
Alle Arten von Booleschen Ausdrücken können zu einer Verzweigung führen. Ich würde zuerst zerlegen, bevor ich seltsame manuelle Optimierungstricks anwende.
Lundin
1
@Lundin, klar, deshalb habe ich "Auf einigen Architekturen" geschrieben ...
Alex Lop.
1
Es ist hauptsächlich eine Frage des Optimierers des Compilers und nicht so sehr der ISA selbst.
Lundin
1
@ Lundin Ich bin anderer Meinung. Einige Architekturen verfügen über ISA für das Setzen / Zurücksetzen von bedingten Bits. In diesem Fall wird kein Brach benötigt, andere nicht. godbolt.org/z/4FDzvw
Alex Lop.
Hinweis: Wenn Sie dies tun, definieren Sie bitte AMAZING_FLAGin Bezug auf AMAZING_FLAG_IDX(z. B. #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), damit Sie nicht dieselben Daten an zwei Stellen definiert haben, sodass eine aktualisiert werden kann (z. B. von 0x4bis 0x8), während die andere ( IDXvon 2) unverändert bleibt .
ShadowRanger
-1

Es gibt mehrere Möglichkeiten, diesen Test durchzuführen:

Der ternäre Operator kann kostspielige Sprünge erzeugen:

if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0))

Sie können auch die boolesche Konvertierung verwenden, die möglicherweise effizient ist oder nicht:

if (!!(flags & AMAZING_FLAG) != doSet)

Oder eine gleichwertige Alternative:

if (((flags & AMAZING_FLAG) != 0) != doSet)

Wenn die Multiplikation billig ist, können Sie Sprünge vermeiden mit:

if ((flags & AMAZING_FLAG) != doSet * AMAZING_FLAG)

Wenn flagses nicht signiert ist und der Compiler sehr intelligent ist, kann die folgende Unterteilung zu einer einfachen Verschiebung kompiliert werden:

if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)

Wenn die Architektur die Zweierkomplementarithmetik verwendet, gibt es hier eine andere Alternative:

if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))

Alternativ könnte man flagseine Struktur mit Bitfeldern definieren und eine viel einfachere und lesbare Syntax verwenden:

if (flags.amazing_flag != doSet)

Leider wird dieser Ansatz normalerweise verpönt, da die Spezifikation von Bitfeldern keine genaue Kontrolle über die Implementierung auf Bitebene ermöglicht.

chqrlie
quelle
Das Hauptziel eines jeden Codes sollte die Lesbarkeit sein, was die meisten Ihrer Beispiele nicht sind. Ein noch größeres Problem tritt auf, wenn Sie Ihre Beispiele tatsächlich in einen Compiler laden: Die ersten beiden Beispielimplementierungen weisen die beste Leistung auf: die geringste Anzahl von Sprüngen und Lasten (ARM 8.2, -Os) (sie sind äquivalent). Alle anderen Implementierungen schneiden schlechter ab. Im Allgemeinen sollte man es vermeiden, ausgefallene Dinge zu tun (Mikrooptimierung), da es für den Compiler schwieriger wird, herauszufinden, was vor sich geht, was die Leistung beeinträchtigt. Daher -1.
Cheiron