Порівнюючи трохи з булевим


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 ((flags & AMAZING_FLAG) != doSet){

}

Але це насправді не працює, trueсхоже, дорівнює 0x01.

Отже, чи є акуратний спосіб трохи порівняти булевий?


2
Не зовсім зрозуміло, якою має бути ваша логіка. Це це (flags & AMAZING_FLAG) && doSet:?
кайлум

Питання не ясно. Ми хочемо перевірити, чи є "прапорами" 0x01 чи ні. Це ти хочеш? Якщо так, то ми можемо використовувати побітовий оператор '&'.
Вікас Віджаян

якщо ви хочете зробити його більш читабельним набором викликуAmazingFlag лише тоді, коли doSet є істинним, тоді ім'я функції перевіряється краще, інакше у вас є функція, яка може чи не може робити те, що говорить ім'я, робить для поганого читання коду
AndersK

Якщо все, що ви намагаєтеся зробити, це зробити його більш читабельним, просто зробіть функцію `flagsNotEqual``.
Jeroen3

Відповіді:


18

Щоб перетворити будь-яке ненульове число в 1 (вірно), є стара хитрість: !двічі застосуйте оператор (не).

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

4
Або (bool)(flags & AMAZING_FLAG) != doSet- що я вважаю більш прямим. (Хоча це говорить про те, що містер Майкрософт має з цим проблему.) Крім того, ((flags & AMAZING_FLAG) != 0)це, мабуть, саме те, що він компілює і є абсолютно явним.
Кріс Холл

"Крім того, ((прапори & AMAZING_FLAG)! = 0), мабуть, саме те, що він компілює, і абсолютно явне". Було б? Що робити, якщо doSet відповідає дійсності?
Чейрон

@ChrisHall Чи (bool) 2 призводить до 1, чи це все ще 2, в C?
користувач253751

3
C11 Стандарт, 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)

У C можуть бути абревіатури через неявні правила перетворення чисел у булі значення. Тож ви могли також писати

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 біта). Це можна легко досягти за допомогою astatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");


1
Будь-які булеві вирази мають потенціал отримати гілку. Спершу я би розібрав, перш ніж застосувати дивні прийоми оптимізації вручну.
Лундін

1
@Lundin, звичайно, тому я написав "Про деякі архітектури" ...
Олексій Лоп.

1
Це здебільшого питання оптимізатора компілятора, а не стільки самого ISA.
Лундін

1
@Lundin Я не згоден. Деякі архітектури мають 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, -O) (вони еквівалентні). Усі інші реалізації працюють гірше. Взагалі слід уникати фантазійних речей (мікрооптимізації), тому що вам буде складніше зрозуміти, що відбувається, а це погіршує продуктивність. Тому -1.
Шейрон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.