Сравнение немного с логическим - PullRequest
12 голосов
/ 20 февраля 2020

Скажем, у меня есть набор флагов, закодированных в uint16_t flags. Например, AMAZING_FLAG = 0x02. Теперь у меня есть функция. Эта функция должна проверить, хочу ли я изменить флаг, потому что если я хочу это сделать, мне нужно записать в fla sh. И это дорого. Поэтому я хочу чек, который говорит мне, если flags & AMAZING_FLAG равно doSet. Это первая идея:

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

Это не интуитивное утверждение if. Я чувствую, что должен быть лучший способ, что-то вроде:

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

}

Но на самом деле это не работает, true кажется равным 0x01.

Итак, Есть хороший способ сравнить немного с логическим значением?

Ответы [ 5 ]

18 голосов
/ 20 февраля 2020

Чтобы преобразовать любое ненулевое число в 1 (true), существует старый прием: дважды примените оператор ! (not).

if (!!(flags & AMAZING_FLAG) != doSet){
2 голосов
/ 20 февраля 2020

Вам необходимо преобразовать битовую маску в логическое выражение, которое в C эквивалентно значениям 0 или 1.

  • (flags & AMAZING_FLAG) != 0. Самый распространенный способ.

  • !!(flags & AMAZING_FLAG). Несколько распространенный, также хорошо, чтобы использовать, но немного крипти c.

  • (bool)(flags & AMAZING_FLAG). Современный C путь от C99 и выше.

Выберите любой из вышеперечисленных вариантов, затем сравните его с вашим логическим значением, используя != или ==.

1 голос
/ 20 февраля 2020

Вы можете создать маску на основе значения doSet:

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

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Теперь ваша проверка может выглядеть следующим образом:

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

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

На некоторых архитектурах !! может быть скомпилировано в ветку, и таким образом вы можете иметь две ветви:

  1. Нормализация по !!(expr)
  2. Сравнить с doSet

Преимущество моего предложения - гарантированная одиночная ветвь.

Примечание: убедитесь, что вы не вводите неопределенное поведение, сдвигая влево более чем на 30 (при условии, что целое число равно 32 битам). Это может быть легко достигнуто с помощью static_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");

1 голос
/ 20 февраля 2020

С логической точки зрения, flags & AMAZING_FLAG - это всего лишь битовая операция, маскирующая все остальные флаги. Результатом является числовое значение.

Чтобы получить логическое значение, вы должны использовать сравнение

(flags & AMAZING_FLAG) == AMAZING_FLAG

и теперь можете сравнить это логическое значение с doSet.

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

В C могут быть сокращения из-за неявных правил преобразования чисел в логические значения. Таким образом, вы можете написать

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

, чтобы написать это более кратко. Но предыдущая версия лучше с точки зрения читабельности.

0 голосов
/ 15 марта 2020

Существует несколько способов выполнить этот тест:

Тернарный оператор может генерировать дорогостоящие скачки:

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

Вы также можете использовать логическое преобразование, которое может быть или не быть эффективным:

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

Или эквивалентная ему альтернатива:

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

Если умножение дешево, вы можете избежать переходов с помощью:

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

Если flags не подписано и компилятор очень умный, приведенное ниже деление может скомпилировать простой сдвиг:

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

Если в архитектуре используется арифметика с двумя дополнениями c, здесь есть другая альтернатива:

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

Альтернативно, можно определить flags как структуру с битовыми полями и использовать намного более простой читаемый синтаксис:

if (flags.amazing_flag != doSet)

Увы, этот подход обычно не одобряется, потому что спецификация битовых полей не позволяет точно контролировать реализация на уровне битов.

...