Сравнение немного с логическим

12

Скажем, у меня есть набор флагов, закодированных в uint16_t flags. Например, AMAZING_FLAG = 0x02. Теперь у меня есть функция. Эта функция должна проверить, хочу ли я изменить флаг, потому что, если я хочу это сделать, мне нужно записать на флэш-память. И это дорого. Поэтому я хочу чек, который говорит мне, 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.

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

Cheiron
источник
2
Не совсем понятно, какой должна быть ваша логика. Это так (flags & AMAZING_FLAG) && doSet?
Кайлум
Вопрос не понятен. Мы хотим проверить, является ли 'flags' 0x01 или нет. Это ты хочешь? Если да, то мы можем использовать побитовый оператор '&'.
Викас Виджаян
если вы хотите сделать его более читабельным, вызовите setAmazingFlag только тогда, когда doSet имеет значение true, тогда имя функции проверяется лучше, в противном случае у вас есть функция, которая может или не может делать то, что говорит это имя, что делает для плохого чтения кода
AndersK
Если все, что вы пытаетесь сделать, это сделать его более читабельным, просто сделайте функцию `flagsNotEqual``.
Jeroen3

Ответы:

18

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

if (!!(flags & AMAZING_FLAG) != doSet){
user253751
источник
4
Или (bool)(flags & AMAZING_FLAG) != doSet- что я считаю более прямым. (Хотя это говорит о том, что у мистера Майкрософт есть проблема с этим.) Кроме того, ((flags & AMAZING_FLAG) != 0)это, вероятно, именно то, к чему он компилируется, и является абсолютно явным.
Крис Холл
«Кроме того, ((flags & AMAZING_FLAG)! = 0), вероятно, именно то, к чему он компилируется и является абсолютно явным». Будет ли это? Что если doSet верен?
Cheiron
@ChrisHall (bool) 2 приводит к 1 или это все еще 2, в C?
user253751
3
C11 Standard, 6.3.1.2 Логический тип: «Когда любое скалярное значение преобразуется в _Bool, результат равен 0, если значение сравнивается равным 0; в противном случае результат равен 1». И 6.5.4 Операторы приведения: «Перед выражением с помощью имени типа в скобках преобразуется значение выражения в именованный тип».
Крис Холл
@Cheiron, чтобы быть более понятным: ((bool)(flags & AMAZING_FLAG) != doSet)имеет тот же эффект, что (((flags & AMAZING_FLAG) != 0) != doSet)и оба они, вероятно, будут компилировать в одно и то же. Предложенное (!!(flags & AMAZING_FLAG) != doSet)эквивалентно, и я думаю, что скомпилируется с тем же. Это вопрос вкуса, который, на ваш взгляд, более понятен: (bool)актеры требуют, чтобы вы помнили, как работают преобразования и преобразования в _Bool; Это !!требует некоторой другой умственной гимнастики, или для вас, чтобы узнать "хитрость".
Крис Холл
2

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

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

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

  • (bool)(flags & AMAZING_FLAG), Современный C-путь только от C99.

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

Лундин
источник
1

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

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

(flags & AMAZING_FLAG) == AMAZING_FLAG

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

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

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

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

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

Ctx
источник
1

Вы можете создать маску на основе 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
Всевозможные логические выражения могут привести к переходу. Я бы сначала разбирал, прежде чем применять странные приемы ручной оптимизации.
Лундин
1
@Lundin, конечно, поэтому я написал «О некоторых архитектурах» ...
Алекс Лоп.
1
Это в основном вопрос для оптимизатора компилятора, а не для самой ISA.
Лундин
1
@ Лундин Я не согласен. Некоторые архитектуры имеют ISA для установки / сброса условных битов, в этом случае нет необходимости в скобках, другие нет. godbolt.org/z/4FDzvw
Алекс Лоп.
Примечание. Если вы сделаете это, пожалуйста, определите AMAZING_FLAGв терминах AMAZING_FLAG_IDX(например #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), чтобы у вас не было одинаковых данных, определенных в двух местах, так что одно можно было бы обновить (скажем, от 0x4до 0x8), а другое ( IDXиз 2) оставить без изменений. ,
ShadowRanger
-1

Есть несколько способов выполнить этот тест:

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

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)

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

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

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

if (flags.amazing_flag != doSet)

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

chqrlie
источник
Основной целью любого фрагмента кода должна быть читабельность, чего нет в большинстве ваших примеров. Еще большая проблема возникает, когда вы фактически загружаете свои примеры в компилятор: первые две реализации примеров имеют лучшую производительность: наименьшее количество переходов и нагрузок (ARM 8.2, -Os) (они эквивалентны). Все остальные реализации работают хуже. В общем, следует избегать излишних мелочей (микрооптимизации), потому что компилятору будет сложнее понять, что происходит, что снижает производительность. Следовательно, -1.
Cheiron