Comparando un poco a un booleano


12

Digamos que tengo un conjunto de banderas, codificadas en uint16_t flags. Por ejemplo, AMAZING_FLAG = 0x02. Ahora tengo una función. Esta función necesita verificar si quiero cambiar la bandera, porque si quiero hacer eso, necesito escribir en flash. Y eso es caro. Por lo tanto, quiero un cheque que me diga si flags & AMAZING_FLAGes igual a doSet. Esta es la primera idea:

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

Esta no es una declaración if intuitiva. Siento que debería haber una mejor manera, algo como:

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

}

Pero esto en realidad no funciona, trueparece ser igual a 0x01.

Entonces, ¿hay una manera ordenada de comparar un poco con un booleano?


2
No está del todo claro cuál debe ser su lógica. ¿Es esto (flags & AMAZING_FLAG) && doSet:?
kaylum

La pregunta no es clara. Queremos verificar si las 'banderas' son 0x01 o no. ¿Eso es lo que quieres? En caso afirmativo, entonces podemos usar el operador bit a bit '&'.
Vikas Vijayan

si desea que sea más legible, llame a setAmazingFlag solo cuando doSet es verdadero, entonces el nombre de la función se verifica mejor; de lo contrario, tiene una función que puede o no hacer lo que dice el nombre, lo que hace que la lectura del código sea incorrecta
AndersK

Si todo lo que intenta hacer es hacerlo más legible, simplemente haga una función `flagsNotEqual``.
Jeroen3

Respuestas:


18

Para convertir cualquier número que no sea cero a 1 (verdadero), hay un viejo truco: aplique el !operador (no) dos veces.

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

44
O (bool)(flags & AMAZING_FLAG) != doSet, lo que me parece más directo. (Aunque esto sugiere que el Sr. Microsoft tiene un problema con esto). Además, ((flags & AMAZING_FLAG) != 0)es probablemente exactamente lo que compila y es absolutamente explícito.
Chris Hall

"Además, ((flags & AMAZING_FLAG)! = 0) es probablemente exactamente lo que compila y es absolutamente explícito". ¿Verdad? ¿Qué pasa si doSet es verdadero?
Cheiron

@ChrisHall ¿(bool) 2 resulta en 1 o sigue siendo 2, en C?
user253751

3
C11 Standard, 6.3.1.2 Tipo booleano: "Cuando cualquier valor escalar se convierte en _Bool, el resultado es 0 si el valor es igual a 0; de lo contrario, el resultado es 1.". Y 6.5.4 Operadores de conversión: "Preceder una expresión por un nombre de tipo entre paréntesis convierte el valor de la expresión al tipo con nombre".
Chris Hall

@Cheiron, para ser más claros: ((bool)(flags & AMAZING_FLAG) != doSet)tiene el mismo efecto (((flags & AMAZING_FLAG) != 0) != doSet)y probablemente ambos compilarán exactamente lo mismo. Lo sugerido (!!(flags & AMAZING_FLAG) != doSet)es equivalente, e imagino que también compilará lo mismo. Es una cuestión de gustos cuál crees que es más claro: el (bool)reparto requiere que recuerdes cómo funcionan los conversiones y conversiones a _Bool; la !!requiera otro tipo de gimnasia mental, o que sepa el "truco".
Chris Hall

2

Necesita convertir la máscara de bits en una declaración booleana, que en C es equivalente a valores 0o 1.

  • (flags & AMAZING_FLAG) != 0. La forma mas comun.

  • !!(flags & AMAZING_FLAG). Algo común, también está bien usar, pero un poco críptico.

  • (bool)(flags & AMAZING_FLAG). Moderno estilo C desde C99 y más allá solamente.

Tome cualquiera de las alternativas anteriores, luego compárela con su valor booleano usando !=o ==.


1

Desde un punto de vista lógico, flags & AMAZING_FLAGes solo una operación de bits que enmascara todos los demás indicadores. El resultado es un valor numérico.

Para recibir un valor booleano, usaría una comparación

(flags & AMAZING_FLAG) == AMAZING_FLAG

y ahora puede comparar este valor lógico con doSet.

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

En C puede haber abreviaturas, debido a las reglas de conversión implícitas de números a valores booleanos. Entonces también podrías escribir

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

para escribir eso más conciso. Pero la versión anterior es mejor en términos de legibilidad.


1

Puede crear una máscara basada en el doSetvalor:

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

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Ahora su cheque puede verse así:

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

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

En algunas arquitecturas, !!puede compilarse en una rama y, de este modo, puede tener dos ramas:

  1. Normalización por !!(expr)
  2. Comparar con doSet

La ventaja de mi propuesta es una rama única garantizada.

Nota: asegúrese de no introducir un comportamiento indefinido desplazándose a la izquierda más de 30 (suponiendo que el entero sea de 32 bits). Esto se puede lograr fácilmente mediante unstatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");


1
Toda clase de expresiones booleanas tiene el potencial de resultar en una rama. Primero desarmaría, antes de aplicar extraños trucos de optimización manual.
Lundin

1
@Lundin, claro, por eso escribí "Sobre algunas arquitecturas" ...
Alex Lop.

1
Es principalmente un asunto del optimizador del compilador y no tanto del ISA en sí.
Lundin

1
@Lundin no estoy de acuerdo. Algunas arquitecturas tienen ISA para establecer / restablecer bits condicional, en cuyo caso no se necesita brach, otras no. godbolt.org/z/4FDzvw
Alex Lop.

Nota: Si hace esto, defina AMAZING_FLAGen términos de AMAZING_FLAG_IDX(p #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX)). Ej. ), Para que no tenga los mismos datos definidos en dos lugares, de modo que uno pueda actualizarse (digamos, de 0x4a 0x8) mientras que el otro ( IDXde 2) no se modifica .
ShadowRanger

-1

Hay varias formas de realizar esta prueba:

El operador ternario podría generar saltos costosos:

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

También puede usar la conversión booleana, que puede o no ser eficiente:

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

O su alternativa equivalente:

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

Si la multiplicación es barata, puede evitar saltos con:

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

Si flagsno está firmado y el compilador es muy inteligente, la siguiente división podría compilarse con un simple cambio:

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

Si la arquitectura usa la aritmética del complemento a dos, aquí hay otra alternativa:

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

Alternativamente, uno podría definir flagscomo una estructura con campos de bits y usar una sintaxis mucho más simple y legible:

if (flags.amazing_flag != doSet)

Por desgracia, este enfoque generalmente está mal visto porque la especificación de los campos de bits no permite un control preciso sobre la implementación a nivel de bits.


El objetivo principal de cualquier pieza de código debe ser la legibilidad, que no son la mayoría de sus ejemplos. Un problema aún mayor surge cuando realmente carga sus ejemplos en un compilador: las dos implementaciones de ejemplo iniciales tienen el mejor rendimiento: menor cantidad de saltos y cargas (ARM 8.2, -Os) (son equivalentes). Todas las demás implementaciones funcionan peor. En general, se debe evitar hacer cosas sofisticadas (micro-optimización), porque dificultará que el compilador descubra lo que está sucediendo, lo que degrada el rendimiento. Por lo tanto, -1.
Cheiron
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.