Comparer un peu à un booléen

12

Disons que j'ai un ensemble de drapeaux, encodés dans un uint16_t flags. Par exemple AMAZING_FLAG = 0x02,. Maintenant, j'ai une fonction. Cette fonction doit vérifier si je veux changer le drapeau, car si je veux le faire, je dois écrire en flash. Et c'est cher. Par conséquent, je veux un chèque qui me dit si flags & AMAZING_FLAGest égal à doSet. Ceci est la première idée:

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

Ce n'est pas une instruction if intuitive. Je pense qu'il devrait y avoir un meilleur moyen, quelque chose comme:

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

}

Mais cela ne fonctionne pas réellement, truesemble être égal à 0x01.

Alors, y a-t-il un bon moyen de comparer un peu à un booléen?

Cheiron
la source
2
Ce n'est pas tout à fait clair quelle doit être votre logique. Est-ce ceci (flags & AMAZING_FLAG) && doSet:?
kaylum
La question n'est pas claire. Nous voulons vérifier si les «drapeaux» sont 0x01 ou non. C'est ça que tu veux? Si oui, alors nous pouvons utiliser l'opérateur au niveau du bit '&'.
Vikas Vijayan
si vous voulez le rendre plus lisible, appelez setAmazingFlag uniquement lorsque doSet est vrai, alors le nom de la fonction vérifie mieux sinon vous avez une fonction qui peut ou non faire ce que dit le nom, fait une mauvaise lecture de code
AndersK
Si tout ce que vous essayez de faire est de le rendre plus lisible, créez simplement une fonction `flagsNotEqual``.
Jeroen3

Réponses:

18

Pour convertir un nombre non nul en 1 (vrai), il existe une vieille astuce: appliquer l' !opérateur (pas) deux fois.

if (!!(flags & AMAZING_FLAG) != doSet){
user253751
la source
4
Ou (bool)(flags & AMAZING_FLAG) != doSet- que je trouve plus direct. (Bien que cela suggère que M. Microsoft a un problème avec cela.) En outre, ((flags & AMAZING_FLAG) != 0)c'est probablement exactement ce qu'il compile et est absolument explicite.
Chris Hall
"De plus, ((flags & AMAZING_FLAG)! = 0) est probablement exactement ce qu'il compile et est absolument explicite". Est-ce que cela serait? Et si doSet est vrai?
Cheiron
@ChrisHall Est-ce que (bool) 2 donne 1 ou est-il toujours 2, en C?
user253751
3
C11 Standard, 6.3.1.2 Type booléen: "Lorsqu'une valeur scalaire est convertie en _Bool, le résultat est 0 si la valeur est égale à 0; sinon, le résultat est 1.". Et 6.5.4 Opérateurs de transtypage: "Faire précéder une expression d'un nom de type entre parenthèses convertit la valeur de l'expression en type nommé."
Chris Hall
@Cheiron, pour être plus clair: ((bool)(flags & AMAZING_FLAG) != doSet)a le même effet que (((flags & AMAZING_FLAG) != 0) != doSet)et ils compileront probablement exactement la même chose. Le suggéré (!!(flags & AMAZING_FLAG) != doSet)est équivalent, et j'imagine qu'il compilera également la même chose. C'est entièrement une question de goût que vous pensez être plus claire: le (bool)casting vous oblige à vous rappeler comment fonctionnent les conversions et les conversions en _Bool; cela !!nécessite une autre gymnastique mentale, ou pour que vous connaissiez le "truc".
Chris Hall
2

Vous devez convertir le masque de bits en une instruction booléenne, qui en C équivaut à des valeurs 0ou 1.

  • (flags & AMAZING_FLAG) != 0. La manière la plus courante.

  • !!(flags & AMAZING_FLAG). Un peu commun, également OK à utiliser, mais un peu cryptique.

  • (bool)(flags & AMAZING_FLAG). Chemin C moderne à partir de C99 et au-delà uniquement.

Prenez l'une des alternatives ci-dessus, puis comparez-la avec votre booléen en utilisant !=ou ==.

Lundin
la source
1

D'un point de vue logique, ce flags & AMAZING_FLAGn'est qu'une opération de bits masquant tous les autres drapeaux. Le résultat est une valeur numérique.

Pour recevoir une valeur booléenne, vous utiliseriez une comparaison

(flags & AMAZING_FLAG) == AMAZING_FLAG

et peut maintenant comparer cette valeur logique à doSet.

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

En C, il peut y avoir des abréviations, en raison des règles de conversion implicites des nombres en valeurs booléennes. Vous pouvez donc aussi écrire

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

d'écrire que plus laconique. Mais l'ancienne version est meilleure en termes de lisibilité.

Ctx
la source
1

Vous pouvez créer un masque basé sur la doSetvaleur:

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

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Maintenant, votre chèque peut ressembler à ceci:

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

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

Sur certaines architectures, !!peut être compilé en une branche et par cela, vous pouvez avoir deux branches:

  1. Normalisation par !!(expr)
  2. Comparer aux doSet

L'avantage de ma proposition est une seule succursale garantie.

Remarque: assurez-vous de ne pas introduire de comportement indéfini en décalant vers la gauche de plus de 30 (en supposant que l'entier est de 32 bits). Ceci peut être facilement réalisé par unstatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");

Alex Lop.
la source
1
Toutes sortes d'expressions booléennes ont le potentiel d'entraîner une branche. Je démonterais d'abord, avant d'appliquer d'étranges astuces d'optimisation manuelle.
Lundin
1
@Lundin, bien sûr, c'est pourquoi j'ai écrit "Sur certaines architectures" ...
Alex Lop.
1
C'est principalement une question pour l'optimiseur du compilateur et pas tellement l'ISA lui-même.
Lundin
1
@Lundin, je ne suis pas d'accord. Certaines architectures ont ISA pour la définition / réinitialisation de bits conditionnelle, auquel cas aucun brach n'est nécessaire, d'autres non. godbolt.org/z/4FDzvw
Alex Lop.
Remarque: Si vous faites cela, veuillez définir AMAZING_FLAGen termes de AMAZING_FLAG_IDX(par exemple #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), afin que vous n'ayez pas les mêmes données définies à deux endroits de sorte que l'une pourrait être mise à jour (par exemple, de 0x4à 0x8) tandis que l'autre ( IDXde 2) reste inchangée .
ShadowRanger
-1

Il existe plusieurs façons d'effectuer ce test:

L'opérateur ternaire peut générer des sauts coûteux:

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

Vous pouvez également utiliser la conversion booléenne, qui peut être efficace ou non:

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

Ou son alternative équivalente:

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

Si la multiplication est bon marché, vous pouvez éviter les sauts avec:

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

Si flagsn'est pas signé et que le compilateur est très intelligent, la division ci-dessous peut se compiler en un simple décalage:

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

Si l'architecture utilise l'arithmétique du complément à deux, voici une autre alternative:

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

Alternativement, on pourrait définir flagscomme une structure avec des champs de bits et utiliser une syntaxe beaucoup plus simple et lisible:

if (flags.amazing_flag != doSet)

Hélas, cette approche est généralement mal vue car la spécification des champs de bits ne permet pas un contrôle précis de l'implémentation au niveau des bits.

chqrlie
la source
L'objectif principal de tout morceau de code devrait être la lisibilité, ce que la plupart de vos exemples ne sont pas. Un problème encore plus important survient lorsque vous chargez réellement vos exemples dans un compilateur: les deux premières implémentations d'exemple ont les meilleures performances: le moins de sauts et de charges (ARM 8.2, -Os) (ils sont équivalents). Toutes les autres implémentations fonctionnent moins bien. En général, il faut éviter de faire des trucs fantaisistes (micro-optimisation), car il sera plus difficile pour le compilateur de comprendre ce qui se passe, ce qui dégrade les performances. Par conséquent, -1.
Cheiron