强制使用按位和而不是布尔值和
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;
}
它在做按位与运算。这就是 &
所做的定义,并且编译器正确地实现了按位与的可观察行为。
二进制&
的操作数经过整数提升,所以a
和b
被转换为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
,但我怀疑它会被接受。
可能 编写读取 a
和 b
的表示并在最低字节上执行 &
的代码。然而,这将是危险的;编译器可能决定发送一些不同于您预期的位模式,因为它知道这些是 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
在 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;
}
它在做按位与运算。这就是 &
所做的定义,并且编译器正确地实现了按位与的可观察行为。
二进制&
的操作数经过整数提升,所以a
和b
被转换为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
,但我怀疑它会被接受。
可能 编写读取 a
和 b
的表示并在最低字节上执行 &
的代码。然而,这将是危险的;编译器可能决定发送一些不同于您预期的位模式,因为它知道这些是 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
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