为什么比特操作和乘法在这里优先于一个条件?

我在源代码中发现了这一点操作:

A = 0b0001;
B = 0b0010;
C = 0b0100;

flags |= !!(flags & (A | B)) * C;

我看不出,为什么使用这个复杂的表达式。 flags & (A | B) flags过滤到A | B A | B 现在,如果flags设置为任何true ,则将其转换为true ,否则为falsetrue * C == Cfalse * C == 0 。 仅仅使用flags = flags ? flags | C会慢flags = flags ? flags | C flags = flags ? flags | C flags = flags ? flags | C

在GCC 7.3编译

bitwise:
  mov eax, edi
  mov edx, edi
  or edx, 4
  test al, 3
  cmovne eax, edx
  ret

condition:
  mov eax, edi
  mov edx, 0
  or eax, 4
  test edi, edi
  cmove eax, edx
  ret

Clang 6.0还消除了多余的呼叫:

bitwise:  
  xor eax, eax
  test dil, 3
  setne al
  shl eax, 2
  or eax, edi
  ret

condition:
  mov eax, edi
  or eax, 4
  test edi, edi
  cmove eax, edi
  ret

我要监督一些事情吗?


您在您的版本中遗漏了A|B的测试(由MichaelPetch发现)。 而且,如果测试失败,则您正在清零flags而不是将其保留为未修改状态。 请记住,如果存在AB中的任何A ,则需要通过设置必需/隐含的标志C来影响其他位中的其他标志。 (请参阅您链接的源代码中的注释: Add TRANSFER_BIT if missing (implied)

而等价的三元运算符只是替换布尔乘法hack会是flags |= (flags & (A|B)) ? C : 0 flags |= (flags & (A|B)) ? C : 0

另一种方法是flags = (flags & (A|B)) ? flags|C : flags flags = (flags & (A|B)) ? flags|C : flags ,编译得更好。


我在Godbolt上修复了你的代码以使用正确的表达式,是的,它编译为gcc / clang和MSVC的更好看的asm。 可能这在更大的功能的情况下仍然是正确的,这种更大的功能会带来更多带有flags东西。

看起来这个版本用gcc / clang / MSVC编译最好:

int condition(int flags) {
    flags = (flags&(A|B)) ? flags | C : flags;
    return flags;
}

; gcc/clang both emit

    mov     eax, edi
    or      eax, 4
    test    dil, 3
    cmove   eax, edi
    ret

和MSVC是类似的,但与一个and而不是test ,和一个额外的mov由于破坏性and


铿锵6.0也消除了多余的通话

什么call ? 这段代码中没有函数调用。 你是说clang避免了gcc使用的冗余mov edx, edi ? 是的,海湾合作委员会是愚蠢的。


为什么有人会像他们那样写它?

也许他们过去的经验是使用旧版编译器,而这些编译器在三进制运算符方面做得不好,或者在没有cmov非x86平台上cmov并且实际上在源代码中使用*运算符是获得无分支asm的唯一方法之一。

我的猜测是,不管谁写了这个技巧,而且还用它来代替三元操作符。 这不是一个好主意,也不会导致更好的代码。

链接地址: http://www.djcxy.com/p/15071.html

上一篇: Why bitoperation and multiplication is prefered here over a condition?

下一篇: How are x86 uops scheduled, exactly?