Еще одно решение для C booleans (немного грязно, но работает):
((unsigned int)(p) <= (unsigned int)(q))
Это работает, поскольку по стандарту C 0
представляет false, а любое другое значение true (1
возвращается для true булевыми операторами, int
тип).
«Грязность» заключается в том, что я использую логические значения (p
и q
) в качестве целых чисел, что противоречит некоторым строгим правилам типизации (таким как MISRA), что ж, это вопрос оптимизации. Вы всегда можете #define
это как макрос, чтобы скрыть грязные вещи.
Для правильных логических p
и q
(имеющих двоичные представления 0
или 1
) это работает. В противном случае T->T
может не дать T
, если p
и q
имеют произвольные ненулевые значения для представления true.
Если вам нужно сохранить только результат, начиная с Pentium II, есть инструкция cmovcc
(условное перемещение) (как показано в ответе Дерберта). Для логических значений, однако, даже у 386 была опция без ветвей, инструкция setcc
, которая производит 0
или 1
в месте расположения байта результата (регистр байтов или память). Вы также можете видеть это в ответе Дерберта, и это решение также компилируется в результат, включающий setcc
(setbe
: Устанавливается, если ниже или равно).
Вариант ~p | q
Дерберта и Криса Долана должен быть самым быстрым для обработки больших объемов данных, поскольку он может обрабатывать значения для всех битов p
и q
по отдельности.
Обратите внимание, что даже решение !p || q
не компилируется с переходным кодом на x86: оно использует инструкции setcc
. Это лучшее решение, если p
или q
могут содержать произвольные ненулевые значения, представляющие значение true. Если вы используете тип _Bool
, он сгенерирует очень мало инструкций.
При компиляции для x86 я получил следующие цифры:
__attribute__((fastcall)) int imp1(int a, int b)
{
return ((unsigned int)(a) <= (unsigned int)(b));
}
__attribute__((fastcall)) int imp2(int a, int b)
{
return (!a || b);
}
__attribute__((fastcall)) _Bool imp3(_Bool a, _Bool b)
{
return (!a || b);
}
__attribute__((fastcall)) int imp4(int a, int b)
{
return (~a | b);
}
Результат сборки:
00000000 <imp1>:
0: 31 c0 xor %eax,%eax
2: 39 d1 cmp %edx,%ecx
4: 0f 96 c0 setbe %al
7: c3 ret
00000010 <imp2>:
10: 85 d2 test %edx,%edx
12: 0f 95 c0 setne %al
15: 85 c9 test %ecx,%ecx
17: 0f 94 c2 sete %dl
1a: 09 d0 or %edx,%eax
1c: 0f b6 c0 movzbl %al,%eax
1f: c3 ret
00000020 <imp3>:
20: 89 c8 mov %ecx,%eax
22: 83 f0 01 xor $0x1,%eax
25: 09 d0 or %edx,%eax
27: c3 ret
00000030 <imp4>:
30: 89 d0 mov %edx,%eax
32: f7 d1 not %ecx
34: 09 c8 or %ecx,%eax
36: c3 ret
При использовании типа _Bool
компилятор явно использует, что у него есть только два возможных значения (0
для false и 1
для true), что дает результат, очень похожий на решение ~a | b
(единственное разница в том, что последний выполняет дополнение для всех битов, а не только младший бит).
Компиляция для 64 бит дает примерно такие же результаты.
В любом случае, ясно, что метод не имеет значения с точки зрения избежания создания условных выражений.