Porównywanie trochę do wartości logicznej

12

Powiedzmy, że mam zestaw flag zakodowanych w uint16_t flags. Na przykład AMAZING_FLAG = 0x02. Teraz mam funkcję. Ta funkcja musi sprawdzić, czy chcę zmienić flagę, ponieważ jeśli chcę to zrobić, muszę napisać, aby flashować. A to jest drogie. Dlatego chcę testu, który mówi mi, czy flags & AMAZING_FLAGjest równy doSet. To jest pierwszy pomysł:

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

Nie jest to intuicyjna instrukcja if. Wydaje mi się, że powinien istnieć lepszy sposób, na przykład:

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

}

Ale to tak naprawdę nie działa, truewydaje się być równe 0x01.

Czy istnieje dobry sposób na porównanie z wartością logiczną?

Cheiron
źródło
2
Nie jest do końca jasne, jaka powinna być twoja logika. Czy to jest to (flags & AMAZING_FLAG) && doSet:?
kaylum
Pytanie nie jest jasne. Chcemy sprawdzić, czy „flagi” to 0x01, czy nie. Czy to chcesz? Jeśli tak, to możemy użyć operatora bitowego „&”.
Vikas Vijayan
jeśli chcesz, aby było bardziej czytelne wywołanie setAmazingFlag tylko wtedy, gdy doSet jest prawdziwe, wtedy nazwa funkcji sprawdza się lepiej, w przeciwnym razie masz funkcję, która może, ale nie musi, robić to, co mówi nazwa, powoduje zły odczyt kodu
AndersK
Jeśli wszystko, co próbujesz zrobić, to uczynić go bardziej czytelnym, po prostu utwórz funkcję `flagsNotEqual``.
Jeroen3

Odpowiedzi:

18

Aby przekonwertować dowolną niezerową liczbę na 1 (prawda), istnieje stara sztuczka: zastosuj !(nie) operator dwa razy.

if (!!(flags & AMAZING_FLAG) != doSet){
użytkownik253751
źródło
4
Lub (bool)(flags & AMAZING_FLAG) != doSet- co uważam za bardziej bezpośrednie. (Chociaż sugeruje to , że pan Microsoft ma z tym problem.) Również ((flags & AMAZING_FLAG) != 0)prawdopodobnie jest dokładnie tym, do czego się kompiluje i jest absolutnie wyraźny.
Chris Hall
„Ponadto ((flagi i AMAZING_FLAG)! = 0) jest prawdopodobnie dokładnie tym, do czego się kompiluje i jest całkowicie jednoznaczne”. Czy to by było? Co jeśli doSet jest prawdą?
Cheiron
@ChrisHall Czy (bool) 2 daje 1, czy nadal 2, w C?
user253751
3
C11 Standard, 6.3.1.2 Typ boolowski: „Kiedy dowolna wartość skalarna jest konwertowana na _Bool, wynikiem jest 0, jeśli wartość jest równa 0; w przeciwnym razie wynikiem jest 1.”. I 6.5.4 Operatory rzutowania: „Poprzedzenie wyrażenia nazwą w nawiasie okrągłym przekształca wartość wyrażenia na nazwany typ”.
Chris Hall
@Cheiron, aby być bardziej zrozumiałym: ((bool)(flags & AMAZING_FLAG) != doSet)ma taki sam efekt jak (((flags & AMAZING_FLAG) != 0) != doSet)i oboje prawdopodobnie skompilują się do dokładnie tego samego. Sugerowane (!!(flags & AMAZING_FLAG) != doSet)jest równoważne i wyobrażam sobie, że skompiluje się również do tego samego. To kwestia gustu, która według Ciebie jest wyraźniejsza: (bool)obsada wymaga zapamiętania, jak działają rzutowania i konwersje do _Bool; !!wymaga pewnych innych gimnastykę umysłu, albo, aby poznać „trick”.
Chris Hall
2

Musisz przekonwertować maskę bitową na instrukcję boolean, która w C jest równoważna wartościom 0lub 1.

  • (flags & AMAZING_FLAG) != 0. Najczęstszy sposób.

  • !!(flags & AMAZING_FLAG). Nieco powszechne, również OK, aby użyć, ale nieco tajemnicze.

  • (bool)(flags & AMAZING_FLAG). Nowoczesny sposób C tylko z C99 i nie tylko.

Wybierz dowolną z powyższych opcji, a następnie porównaj ją z wartością logiczną za pomocą !=lub ==.

Lundin
źródło
1

Z logicznego punktu widzenia flags & AMAZING_FLAGjest to tylko niewielka operacja maskująca wszystkie inne flagi. Wynik jest wartością liczbową.

Aby otrzymać wartość logiczną, użyłbyś porównania

(flags & AMAZING_FLAG) == AMAZING_FLAG

i może teraz porównać tę wartość logiczną z doSet.

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

W C mogą występować skróty z powodu niejawnych reguł konwersji liczb na wartości boolowskie. Więc możesz też pisać

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

napisać to bardziej zwięźle. Ale poprzednia wersja jest lepsza pod względem czytelności.

Ctx
źródło
1

Możesz utworzyć maskę na podstawie doSetwartości:

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

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Teraz czek może wyglądać następująco:

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

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

Na niektórych architekturach !!może być skompilowany do gałęzi, dzięki czemu możesz mieć dwie gałęzie:

  1. Normalizacja przez !!(expr)
  2. Porównać do doSet

Zaletą mojej propozycji jest zagwarantowanie jednego oddziału.

Uwaga: upewnij się, że nie wprowadzasz niezdefiniowanego zachowania, przesuwając w lewo o więcej niż 30 (zakładając, że liczba całkowita to 32 bity). Można to łatwo osiągnąć za pomocąstatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");

Alex Lop.
źródło
1
Wszelkiego rodzaju wyrażenia boolowskie mogą potencjalnie prowadzić do rozgałęzienia. Najpierw zdemontowałem, a potem zastosowałem dziwne ręczne sztuczki optymalizacji.
Lundin
1
@Lundin, jasne, dlatego napisałem „Na niektórych architekturach” ...
Alex Lop.
1
Jest to głównie kwestia optymalizatora kompilatora, a nie samego ISA.
Lundin
1
@Lundin Nie zgadzam się. Niektóre architektury mają ISA do warunkowego ustawiania / resetowania bitów, w którym to przypadku nie jest wymagane żadne wybieranie, inne nie. godbolt.org/z/4FDzvw
Alex Lop.
Uwaga: Jeśli to zrobisz, zdefiniuj AMAZING_FLAGw kategoriach AMAZING_FLAG_IDX(np. #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), Aby nie zdefiniować tych samych danych w dwóch miejscach, tak aby jedno z nich mogło zostać zaktualizowane (powiedzmy od 0x4do 0x8), podczas gdy drugie ( IDXz 2) pozostanie niezmienione .
ShadowRanger
-1

Istnieje wiele sposobów wykonania tego testu:

Operator trójskładnikowy może generować kosztowne skoki:

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

Możesz także użyć konwersji logicznej, która może, ale nie musi być skuteczna:

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

Lub jego odpowiednik:

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

Jeśli mnożenie jest tanie, możesz uniknąć skoków za pomocą:

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

Jeśli nie flagsjest podpisany, a kompilator jest bardzo inteligentny, poniższy podział może się skompilować do prostej zmiany:

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

Jeśli architektura używa arytmetyki dopełniania dwóch, oto kolejna alternatywa:

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

Alternatywnie można zdefiniować flagsjako strukturę z polami bitowymi i zastosować znacznie prostszą czytelną składnię:

if (flags.amazing_flag != doSet)

Niestety, takie podejście jest zwykle lekceważone, ponieważ specyfikacja pól bitowych nie pozwala na precyzyjną kontrolę nad implementacją na poziomie bitów.

chqrlie
źródło
Podstawowym celem każdego fragmentu kodu powinna być czytelność, czego większość przykładów nie jest. Jeszcze większy problem powstaje, gdy faktycznie ładujesz swoje przykłady do kompilatora: dwie pierwsze implementacje przykładowe mają najlepszą wydajność: najmniejszą liczbę skoków i obciążeń (ARM 8.2, -Os) (są one równoważne). Wszystkie inne implementacje działają gorzej. Ogólnie rzecz biorąc, należy unikać robienia wyszukanych rzeczy (mikrooptymalizacja), ponieważ utrudnisz kompilatorowi ustalenie, co się dzieje, co obniża wydajność. Dlatego -1.
Cheiron