Comparando um pouco com um booleano

12

Digamos que eu tenha um conjunto de sinalizadores, codificado em uint16_t flags. Por exemplo AMAZING_FLAG = 0x02,. Agora, eu tenho uma função. Essa função precisa verificar se eu quero alterar o sinalizador, porque se eu quiser fazer isso, preciso escrever para piscar. E isso é caro. Portanto, quero uma verificação que me diga se flags & AMAZING_FLAGé igual a doSet. Esta é a primeira ideia:

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

Esta não é uma declaração intuitiva. Eu sinto que deveria haver uma maneira melhor, algo como:

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

}

Mas isso realmente não funciona, trueparece ser igual a 0x01.

Então, existe uma maneira legal de comparar um pouco com um booleano?

Cheiron
fonte
2
Não está totalmente claro qual deve ser sua lógica. É isso (flags & AMAZING_FLAG) && doSet:?
kaylum
A questão não está clara. Queremos verificar se os 'sinalizadores' são 0x01 ou não. É isso que você quer? Se sim, então podemos usar o operador bit a bit '&'.
Vikas Vijayan
se você quiser torná-lo mais legível setAmazingFlag chamada somente quando doSet é verdade, então o nome da função verifica-se melhor caso contrário, tem uma função que pode ou não pode fazer o que o nome diz, faz uma leitura de código ruim
AndersK
Se tudo o que você está tentando fazer é torná-lo mais legível, basta criar a função `flagsNotEqual``.
Jeroen3 20/02

Respostas:

18

Para converter qualquer número diferente de zero em 1 (verdadeiro), existe um truque antigo: aplique o !operador (não) duas vezes.

if (!!(flags & AMAZING_FLAG) != doSet){
user253751
fonte
4
Ou (bool)(flags & AMAZING_FLAG) != doSet- o que acho mais direto. (Embora isso sugira que o Sr. Microsoft tenha um problema com isso.) Além disso, ((flags & AMAZING_FLAG) != 0)provavelmente é exatamente o que compila e é absolutamente explícito.
Chris Hall
"Além disso, ((flags & AMAZING_FLAG)! = 0) provavelmente é exatamente o que compila e é absolutamente explícito". Seria? E se doSet for verdadeiro?
Cheiron
@ChrisHall (bool) 2 resulta em 1 ou ainda é 2, em C?
user253751 20/02
3
C11 Padrão, 6.3.1.2 Tipo booleano: "Quando qualquer valor escalar é convertido em _Bool, o resultado é 0 se o valor for comparado a 0; caso contrário, o resultado será 1.". E 6.5.4 Operadores de conversão: "Preceder uma expressão por um nome de tipo entre parênteses converte o valor da expressão no tipo nomeado".
Chris Hall
@ Cheiron, para ser mais claro: ((bool)(flags & AMAZING_FLAG) != doSet)tem o mesmo efeito (((flags & AMAZING_FLAG) != 0) != doSet)e ambos provavelmente serão compilados exatamente para a mesma coisa. O sugerido (!!(flags & AMAZING_FLAG) != doSet)é equivalente, e imagino que será compilado também. É inteiramente uma questão de gosto qual você acha que é mais clara: o (bool)elenco exige que você se lembre de como os elencos e conversões para _Bool funcionam; isso !!requer alguma outra ginástica mental, ou para você conhecer o "truque".
Chris Hall
2

Você precisa converter a máscara de bits em uma instrução booleana, que em C é equivalente a valores 0ou 1.

  • (flags & AMAZING_FLAG) != 0. A maneira mais comum.

  • !!(flags & AMAZING_FLAG). Um pouco comum, também é bom de usar, mas um pouco enigmático.

  • (bool)(flags & AMAZING_FLAG). C moderno a partir de C99 e somente além.

Pegue uma das alternativas acima e compare-a com o seu booleano usando !=or ==.

Lundin
fonte
1

Do ponto de vista lógico, flags & AMAZING_FLAG é apenas uma operação pouco mascarando todos os outros sinalizadores. O resultado é um valor numérico.

Para receber um valor booleano, você usaria uma comparação

(flags & AMAZING_FLAG) == AMAZING_FLAG

e agora pode comparar esse valor lógico com doSet.

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

Em C, pode haver abreviações, devido às regras implícitas de conversão de números em valores booleanos. Então você também pode escrever

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

para escrever isso mais conciso. Mas a versão anterior é melhor em termos de legibilidade.

Ctx
fonte
1

Você pode criar uma máscara com base no doSetvalor:

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

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Agora seu cheque pode ficar assim:

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

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

Em algumas arquiteturas, !!pode ser compilado em uma ramificação e, com isso, você pode ter duas ramificações:

  1. Normalização por !!(expr)
  2. Comparado a doSet

A vantagem da minha proposta é uma filial única garantida.

Nota: certifique-se de não introduzir um comportamento indefinido, deslocando a esquerda em mais de 30 (assumindo que o número inteiro seja 32 bits). Isso pode ser facilmente alcançado por umstatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");

Alex Lop.
fonte
11
Todo tipo de expressão booleana tem o potencial de resultar em uma ramificação. Eu desmontaria primeiro, antes de aplicar truques estranhos de otimização manual.
Lundin
11
@Lundin, claro, foi por isso que escrevi "Em algumas arquiteturas" ...
Alex Lop.
11
É principalmente uma questão para o otimizador do compilador e não tanto para o próprio ISA.
Lundin
11
@Lundin eu discordo. Algumas arquiteturas têm ISA para definir / redefinir condicionais bits; nesse caso, nenhum brach é necessário, outras não. godbolt.org/z/4FDzvw
Alex Lop.
Nota: Se você fizer isso, defina AMAZING_FLAGem termos de AMAZING_FLAG_IDX(por exemplo #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), para não ter os mesmos dados definidos em dois locais, para que um possa ser atualizado (digamos, de 0x4para 0x8) enquanto o outro ( IDXde 2) permanece inalterado .
ShadowRanger
-1

Existem várias maneiras de executar este teste:

O operador ternário pode gerar saltos caros:

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

Você também pode usar a conversão booleana, que pode ou não ser eficiente:

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

Ou sua alternativa equivalente:

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

Se a multiplicação for barata, você pode evitar saltos com:

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

Se flagsnão estiver assinado e o compilador for muito inteligente, a divisão abaixo poderá ser compilada para uma mudança simples:

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

Se a arquitetura usa a aritmética de complemento de dois, aqui está outra alternativa:

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

Como alternativa, pode-se definir flagscomo uma estrutura com campos de bits e usar uma sintaxe muito mais simples e legível:

if (flags.amazing_flag != doSet)

Infelizmente, essa abordagem geralmente é desaprovada, porque a especificação de campos de bits não permite controle preciso sobre a implementação no nível de bits.

chqrlie
fonte
O objetivo principal de qualquer parte do código deve ser a legibilidade, o que a maioria dos seus exemplos não é. Um problema ainda maior surge quando você realmente carrega seus exemplos em um compilador: as duas implementações iniciais de exemplo têm o melhor desempenho: menor quantidade de saltos e cargas (ARM 8.2, -Os) (eles são equivalentes). Todas as outras implementações apresentam desempenho pior. Em geral, deve-se evitar fazer coisas sofisticadas (micro-otimização), porque você tornará mais difícil para o compilador descobrir o que está acontecendo, o que prejudica o desempenho. Portanto, -1.
Cheiron