强制使用按位和而不是布尔值和

forcing usage of bitwise and instead of boolean and

在 C++ 中,布尔值保证为 0 或 1

C++ (§4.5/4):
An rvalue of type bool can be converted to an rvalue of type int, with 
false becoming zero and true becoming one.

考虑以下函数以及 g++5.2 使用 -O3 生成的内容

int foo(bool a, bool b)
{
    if(a&b) return 3;
    else return 5;
}


0000000000000000 <_Z3foobb>:
   0:   40 84 ff                test   %dil,%dil
   3:   74 13                   je     18 <_Z3foobb+0x18>
   5:   40 84 f6                test   %sil,%sil
   8:   b8 03 00 00 00          mov    [=11=]x3,%eax
   d:   74 09                   je     18 <_Z3foobb+0x18>
   f:   f3 c3                   repz retq 
   11:  0f 1f 80 00 00 00 00    nopl   0x0(%rax)
   18:  b8 05 00 00 00          mov    [=11=]x5,%eax
   1d:  c3                      retq   

如上所示,它正在生成两条测试指令,这表明它仍在将 if 视为 if(a&&b) 而不是按位与。

即使我首先明确地将两个 bool 转换为两个 char,它仍然会生成与上面相同的输出。

因为我知道a和b这两个操作数只能有0/1作为值,有没有办法让gcc只生成一条测试指令。如果函数采用两个整数而不是两个布尔值,这确实是它所做的。

我觉得这可能不是你要找的东西,但通常是算术运算,并且只有在被测试的对象是 int 类型时才能执行,所以一个简单的转换可能会解决这个问题,但我假设转换可以生成您也不想要的额外不需要的指令。

int foo(bool a, bool b)
{ 
    if((int)a&(int)b) return 3;
    else return 5;
}

它在做按位与运算。这就是 & 所做的定义,并且编译器正确地实现了按位与的可观察行为。

二进制&的操作数经过整数提升,所以ab被转换为int。这个转换的定义是:

  • true 转换为 1
  • false 转换为 0

执行按位与,这里是一个详尽的案例列表:

  • 1 & 1 == 1
  • 1 & 0 == 0
  • 0 & 1 == 0
  • 0 & 0 == 0

如您所见,这与布尔 AND 的情况列表相同。因此,您无法仅通过查看汇编输出来区分操作。

为了证实这一点,here is an example 表明 g++ 为 if (a&b) 生成了与 if (a&&b) 完全相同的程序集。

请记住,C++ 是根据抽象机定义的,编译器的输出可以是任何东西,只要它产生与抽象机相同的可观察行为即可。


您似乎反对与将 bool 转换为 int 相关的代码部分。你的编译器显然已经实现了 bool 这样任何非零位模式都被认为是 true.

因此,需要额外的步骤。例如,在其中一个布尔值具有位模式 000...0010 而另一个具有 000....0001 的情况下,执行 test %dil, %sil 会给出错误的结果。

正如 user657267 在评论中指出的那样,编译器决定在一个参数上测试非零值,然后在另一个参数上测试非零值,很容易证明它满足定义的要求& 的行为以及如上所述的整数提升。

对此您无能为力。您可以提交一个 g++ 补丁,这样它只会为 true 布尔值生成位模式 000...0001,因此它可以在这里使用 test %dil, %sil,但我怀疑它会被接受。


可能 编写读取 ab 的表示并在最低字节上执行 & 的代码。然而,这将是危险的;编译器可能决定发送一些不同于您预期的位模式,因为它知道这些是 true 可接受的位模式。

另一种解决方案可能是将您的函数更改为接受 unsigned int 个参数而不是 bool。当然这会引入新错误的可能性,例如如果您使用 1ull << 40 调用该函数,它会将 1ull << 40 截断为 0,因此与传入 false 的行为相同。您不能吃蛋糕就拥有它;在这种情况下,每次调用函数时都必须非常小心。 (也许为 int 写一个包装器 class 会有所帮助!)

使用 &,一些编译器已经生成了不同的没有跳转的 asm:

clang 3.6 (rc2):

foo(bool, bool):                               # @foo(bool, bool)
    testb   %sil, %dil
    sete    %al
    movzbl  %al, %eax
    leal    3(%rax,%rax), %eax
    retq

一个 hack,在你的情况下是使用 *,它对 true/false

具有相同的 true-table
int foo(bool a, bool b)
{
    if (a * b) return 3;
    else return 5;
}

产生:

foo(bool, bool):
    movzbl  %sil, %esi  # b, b
    movzbl  %dil, %edi  # a, tmp70
    imull   %esi, %edi  # b, tmp70
    cmpl    , %edi    #, tmp70
    sbbl    %eax, %eax  # D.1960
    andl    , %eax    #, D.1960
    addl    , %eax    #, D.1960
    ret

Demo