这是多余的,不是你在优化的 asm 中看到的。
即使cmp/ja 是其他地方的可能跳转目标,现有的优化编译器(如 GCC、clang、MSVC 和 ICC)也会(我很确定)执行jmp 或不同的代码布局,而不是让执行落入一个永远为假的条件分支。优化器会知道在这条执行路径上不需要有条件分支,因此会确保它没有遇到条件分支。 (即使这需要额外花费jmp。)
这可能是一个不错的选择,即使在通过这种方式可以节省一些代码大小的假设情况下,因为您不想用不必要的条件分支污染/稀释分支预测历史,并且分支可能会错误预测为采取了。
但在调试模式下,一些编译器比其他编译器更能够在单个语句或表达式中进行优化。 (Across statements they'd always spill/reload vars to memory,除非你使用了register int foo;)
我能够欺骗 clang -O0 和 MSVC 发出确切的指令序列。还有类似的东西,但来自 GCC 的情况更糟。
(令人惊讶的是,gcc -O0 仍然在单个表达式中进行了一些优化,例如对 x /= 10; 使用乘法逆,并为 if(false) 删除死代码。与 MSVC 实际上将 0 放入寄存器并测试它是否为 0。)
void dummy();
unsigned char set_al();
int foo(void) {
if ((set_al() & 3) <= 3U)
dummy();
return 0;
}
clang12.0 for x86-64 Linux (on Godbolt)
push rbp
mov rbp, rsp
call set_al()
movzx eax, al # The redundant sequence
and eax, 3
cmp eax, 3
ja .LBB0_2
call dummy()
.LBB0_2:
xor eax, eax
pop rbp
ret
MSVC 包含相同的序列。 GCC10.3 类似但更糟糕的是,它在寄存器中实现了一个布尔值并testing 它。 (两者也在同一个 Godbolt 链接中)
## GCC10.3
... set up RBP as a frame pointer
movzx eax, al # The redundant sequence
and eax, 3
cmp eax, 3
setbe al
test al, al # even worse than just jnbe
je .L2
call dummy()
.L2:
mov eax, 0
pop rbp
ret
char 来自内存而不是返回值,即使在调试模式下,GCC 也会优化比较:
int bar(unsigned char *p) {
if ((*p & 3) <= 3U)
dummy();
return 0;
}
# GCC 10.3 -O0
bar(unsigned char*):
push rbp
mov rbp, rsp
sub rsp, 16 # space to spill the function arg
mov QWORD PTR [rbp-8], rdi
call dummy() # unconditional call
mov eax, 0
leave
ret
clang 和 MSVC 进行测试,都使用 asm 之类的
#MSVC19.28 (VS16.9) default options (debug mode)
...
movzx eax, BYTE PTR [rax]
and eax, 3
cmp eax, 3
ja SHORT $LN2@bar
...