Paragonando un po 'a un booleano


12

Supponi di avere un set di flag, codificato in uint16_t flags. Ad esempio AMAZING_FLAG = 0x02,. Ora ho una funzione. Questa funzione deve verificare se voglio cambiare il flag, perché se voglio farlo, devo scrivere in flash. E questo è costoso. Pertanto, voglio un controllo che mi dica se flags & AMAZING_FLAGè uguale a doSet. Questa è la prima idea:

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

Questa non è un'istruzione if intuitiva. Sento che dovrebbe esserci un modo migliore, qualcosa del tipo:

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

}

Ma questo in realtà non funziona, truesembra essere uguale a 0x01.

Quindi, c'è un modo pulito per confrontare un po 'con un booleano?


2
Non è del tutto chiaro quale sia la tua logica. È questo (flags & AMAZING_FLAG) && doSet:?
Kaylum,

La domanda non è chiara Vogliamo verificare se le "bandiere" sono 0x01 o meno. Che vuoi? Se sì, allora possiamo usare l'operatore bit per bit "&".
Vikas Vijayan,

se vuoi renderlo più leggibile chiama setAmazingFlag solo quando doSet è vero, allora il nome della funzione si verifica meglio altrimenti hai una funzione che può o meno fare quello che dice il nome, rende la lettura del codice
errata

Se tutto ciò che stai cercando di fare è renderlo più leggibile, basta fare una funzione `flagsNotEqual``.
Jeroen3,

Risposte:


18

Per convertire qualsiasi numero diverso da zero in 1 (vero), esiste un vecchio trucco: applicare l' !operatore (non) due volte.

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

4
O (bool)(flags & AMAZING_FLAG) != doSet- che trovo più diretto. (Anche se questo suggerisce che il signor Microsoft ha un problema con questo.) Inoltre, ((flags & AMAZING_FLAG) != 0)è probabilmente esattamente ciò che compila ed è assolutamente esplicito.
Chris Hall,

"Inoltre, ((flags & AMAZING_FLAG)! = 0) è probabilmente esattamente ciò che compila ed è assolutamente esplicito". Sarebbe? Cosa succede se doSet è vero?
Cheiron,

@ChrisHall (bool) 2 risulta in 1 o è ancora 2, in C?
user253751

3
C11 Standard, 6.3.1.2 Tipo booleano: "Quando un valore scalare viene convertito in _Bool, il risultato è 0 se il valore confronta uguale a 0; in caso contrario, il risultato è 1.". E 6.5.4 Operatori di cast: "La precedente espressione di un nome di tipo tra parentesi converte il valore dell'espressione nel tipo di nome."
Chris Hall,

@Cheiron, per essere più chiari: ((bool)(flags & AMAZING_FLAG) != doSet)ha lo stesso effetto di (((flags & AMAZING_FLAG) != 0) != doSet)e probabilmente entrambi si compileranno esattamente nella stessa cosa. Il suggerito (!!(flags & AMAZING_FLAG) != doSet)è equivalente e immagino si compili anche con la stessa cosa. È tutta una questione di gusti, che ritieni più chiara: il (bool)cast richiede di ricordare come funzionano i cast e le conversioni in _Bool; la !!richiede alcuni altri ginnastica mentale, o per voi di conoscere il "trucco".
Chris Hall,

2

È necessario convertire la maschera di bit in un'istruzione booleana, che in C è equivalente a valori 0o 1.

  • (flags & AMAZING_FLAG) != 0. Il modo più comune.

  • !!(flags & AMAZING_FLAG). Un po 'comune, anche OK da usare, ma un po' enigmatico.

  • (bool)(flags & AMAZING_FLAG). Moderna via C dalla C99 e oltre.

Prendi una delle alternative sopra, quindi confrontala con il tuo valore booleano usando !=o ==.


1

Da un punto di vista logico, flags & AMAZING_FLAGè solo una piccola operazione che maschera tutti gli altri flag. Il risultato è un valore numerico.

Per ricevere un valore booleano, è necessario utilizzare un confronto

(flags & AMAZING_FLAG) == AMAZING_FLAG

e ora può confrontare questo valore logico con doSet.

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

In C possono esserci abbreviazioni, a causa delle implicite regole di conversione dei numeri in valori booleani. Quindi potresti anche scrivere

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

per scrivere più conciso. Ma la versione precedente è migliore in termini di leggibilità.


1

È possibile creare una maschera in base al doSetvalore:

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

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Ora il tuo assegno può apparire così:

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

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

Su alcune architetture, !!può essere compilato in un ramo e da questo, si possono avere due rami:

  1. Normalizzazione di !!(expr)
  2. Confrontare con doSet

Il vantaggio della mia proposta è un ramo unico garantito.

Nota: assicurati di non introdurre un comportamento indefinito spostando a sinistra di oltre 30 (supponendo che l'intero sia 32 bit). Questo può essere facilmente raggiunto da astatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");


1
Ogni tipo di espressione booleana ha il potenziale di provocare un ramo. Smonterei prima, prima di applicare strani trucchi di ottimizzazione manuale.
Lundin,

1
@Lundin, certo, è per questo che ho scritto "Su alcune architetture" ...
Alex Lop.

1
È principalmente una questione di ottimizzatore del compilatore e non tanto dell'ISA stesso.
Lundin,

1
@Lundin Non sono d'accordo. Alcune architetture hanno ISA per l'inserimento / ripristino di bit condizionato, nel qual caso non è necessario alcun brach, altri no. godbolt.org/z/4FDzvw
Alex Lop.

Nota: se lo fai, ti preghiamo di definire AMAZING_FLAGin termini di AMAZING_FLAG_IDX(ad esempio #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), quindi non hai gli stessi dati definiti in due posizioni in modo tale che uno possa essere aggiornato (diciamo, da 0x4a 0x8) mentre l'altro ( IDXdi 2) rimane invariato .
ShadowRanger

-1

Esistono diversi modi per eseguire questo test:

L'operatore ternario potrebbe generare salti costosi:

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

Puoi anche utilizzare la conversione booleana, che può essere o meno efficiente:

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

O la sua alternativa equivalente:

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

Se la moltiplicazione è economica, puoi evitare i salti con:

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

Se flagsè senza segno e il compilatore è molto intelligente, la divisione di seguito potrebbe compilare in un semplice turno:

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

Se l'architettura utilizza l'aritmetica del complemento a due, ecco un'altra alternativa:

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

In alternativa, si potrebbe definire flagsuna struttura con campi di bit e utilizzare una sintassi molto più semplice e leggibile:

if (flags.amazing_flag != doSet)

Purtroppo, questo approccio è solitamente disapprovato perché le specifiche dei bitfield non consentono un controllo preciso sull'implementazione a livello di bit.


L'obiettivo principale di qualsiasi parte di codice dovrebbe essere la leggibilità, che la maggior parte dei tuoi esempi non lo sono. Un problema ancora più grande si presenta quando carichi effettivamente i tuoi esempi in un compilatore: le prime due implementazioni di esempio hanno le migliori prestazioni: il minor numero di salti e carichi (ARM 8.2, -Os) (sono equivalenti). Tutte le altre implementazioni hanno prestazioni peggiori. In generale, si dovrebbe evitare di fare cose fantasiose (micro-ottimizzazione), perché sarà più difficile per il compilatore capire cosa sta succedendo, il che peggiora le prestazioni. Pertanto, -1.
Cheiron,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.