如何在C中使用按位或其他有效代码实现逻辑含义?

我想实现尽可能高效的逻辑操作。 我需要这个真值表:

pqp → q TTT TFF FTT FFT 

据维基百科称,这被称为“ 逻辑蕴涵 ”

我一直试图弄清楚如何在不使用条件的情况下使用C中的按位运算来实现这一点。 也许有人对此有所了解。

谢谢

仅供参考,gcc-4.3.3:

 int foo(int a, int b) { return !a || b; } int bar(int a, int b) { return ~a | b; } 

给(来自objdump -d):

 0000000000000000 : 0: 85 ff test %edi,%edi 2: 0f 94 c2 sete %dl 5: 85 f6 test %esi,%esi 7: 0f 95 c0 setne %al a: 09 d0 or %edx,%eax c: 83 e0 01 and $0x1,%eax f: c3 retq 0000000000000010 : 10: f7 d7 not %edi 12: 09 fe or %edi,%esi 14: 89 f0 mov %esi,%eax 16: c3 retq 

所以,没有分支,但是指令的两倍。

甚至更好,使用_Bool (感谢@litb):

 _Bool baz(_Bool a, _Bool b) { return !a || b; } 
 0000000000000020 : 20: 40 84 ff test %dil,%dil 23: b8 01 00 00 00 mov $0x1,%eax 28: 0f 45 c6 cmovne %esi,%eax 2b: c3 retq 

因此,使用_Bool而不是int是一个好主意。

由于我今天正在更新,我已经确认gcc 8.2.0会为_Bool:产生类似但不相同的结果_Bool:

 0000000000000020 : 20: 83 f7 01 xor $0x1,%edi 23: 89 f8 mov %edi,%eax 25: 09 f0 or %esi,%eax 27: c3 retq 
 !p || q 

很快。 认真的,不要担心。

 ~p | q 

用于可视化:

 perl -e'printf "%x\n", (~0x1100 | 0x1010) & 0x1111' 1011 

在紧密的代码中,这应该比“!p || q”快,因为后者有一个分支,由于分支预测错误,可能会导致CPU停顿。 按位版本是确定性的,作为奖励,可以在32位整数中执行32倍于布尔版本的工作!

您可以阅读从真值表中导出布尔表达式 (也参见规范forms ),了解如何将任何真值表表达为布尔基元或函数的组合。

C booleans的另一个解决方案(有点脏,但有效):

((unsigned int)(p) <= (unsigned int)(q))

它适用于C标准, 0表示false,任何其他值为true(布尔运算符为int类型返回1 )。

“肮脏”是我使用布尔( pq )作为整数,这与一些强类型政策(如MISRA)相矛盾,这是一个优化问题。 你可能总是将它定义为宏来隐藏脏东西。

对于正确的布尔pq (具有01二进制表示),它可以工作。 否则,如果pq具有用于表示true的任意非零值,则T->T可能无法产生T

如果你只需要存储结果,那么从奔腾II开始,就会有cmovcc (条件移动)指令(如Derobert的回答所示)。 对于布尔值,然而即使是386也setcc选项,即setcc指令,它在结果字节位置(字节寄存器或存储器)中产生01 。 您还可以在Derobert的答案中看到,此解决方案也会编译为涉及setcc的结果( setbe :如果低于或等于则设置)。

Derobert和Chris Dolan的~p | q ~p | q变量应该是处理大量数据的最快的,因为它可以单独处理pq所有位的含义。

请注意,甚至不是!p || q !p || q解决方案编译为x86上的分支代码:它使用setcc指令。 如果pq可能包含表示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); } 

assembly结果:

 00000000 : 0: 31 c0 xor %eax,%eax 2: 39 d1 cmp %edx,%ecx 4: 0f 96 c0 setbe %al 7: c3 ret 00000010 : 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 : 20: 89 c8 mov %ecx,%eax 22: 83 f0 01 xor $0x1,%eax 25: 09 d0 or %edx,%eax 27: c3 ret 00000030 : 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非常相似的结果~a | b ~a | b解决方案(唯一的区别是后者对所有位而不是最低位执行补码)。

对64位进行编译会得到几乎相同的结果。

无论如何,很明显,从避免产生条件的角度来看,该方法并不重要。