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


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.

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


2
Не совсем понятно, какой должна быть ваша логика. Это так (flags & AMAZING_FLAG) && doSet?
— Кайлум

Вопрос не понятен. Мы хотим проверить, является ли 'flags' 0x01 или нет. Это ты хочешь? Если да, то мы можем использовать побитовый оператор '&'.
— Викас Виджаян

если вы хотите сделать его более читабельным, вызовите setAmazingFlag только тогда, когда doSet имеет значение true, тогда имя функции проверяется лучше, в противном случае у вас есть функция, которая может или не может делать то, что говорит это имя, что делает для плохого чтения кода
— AndersK

Если все, что вы пытаетесь сделать, это сделать его более читабельным, просто сделайте функцию `flagsNotEqual``.
— Jeroen3

Ответы:


18

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

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

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)

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


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)

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


Основной целью любого фрагмента кода должна быть читабельность, чего нет в большинстве ваших примеров. Еще большая проблема возникает, когда вы фактически загружаете свои примеры в компилятор: первые две реализации примеров имеют лучшую производительность: наименьшее количество переходов и нагрузок (ARM 8.2, -Os) (они эквивалентны). Все остальные реализации работают хуже. В общем, следует избегать излишних мелочей (микрооптимизации), потому что компилятору будет сложнее понять, что происходит, что снижает производительность. Следовательно, -1.
— Cheiron
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.