问题描述
我知道 C/C++ 使用短路评估来评估布尔表达式。例如,C/C++肯定会在表达式a
中的操作数b
之前计算操作数a && b
,如果a
为假,b
不会进行评估。
另外,我知道像 5==6
这样的东西可能会被编译器完全忽略,因为它是一个常量表达式,可以在编译时计算。
但是不知道编译器能不能优化b && 0
?编译器可以说:好吧,0
的求值比b
的求值容易很多,而且b
没有任何副作用,所以我决定改b && 0
进入 0 && b
以先评估 0
。
解决方法
您的问题涉及两个独立的问题。第一个是当编译器“看到”if
条件总是false
(由于&& 0
)时,它可以完全丢弃相应的分支。示例翻译单元:
bool f(int);
int main()
{
if (f(1) && 0)
return 1;
}
启用优化后,很可能不会为分支生成机器代码。但是,f(1)
表达式仍必须在运行时求值,因为编译器无法证明 f(1)
调用没有可观察到的行为。
机器代码:https://godbolt.org/z/sEMrfh
相反,如果编译器可以证明 f(1)
没有可观察的行为,它可以消除它的调用。这与求值顺序无关,而是与 as-if 规则有关。演示翻译单元:
static bool f(int i)
{
int j = i + 1;
return true;
}
int main()
{
if (f(1) && 0)
return 1;
}
机器代码:https://godbolt.org/z/scs3je
,&&
和 ||
运算符保证从左到右求值。评估意味着编译器必须检查操作数的副作用,如果存在副作用,它必须执行这些。如果 &&
的左操作数计算为零,则不会计算右操作数。
考虑if(func() && 0) { do_stuff(); }
。尽管 &&
表达式永远不可能为真,但仍必须执行该函数。编译器不会做一些奇怪的重新排序,比如 0 && func()
,它只会用 func();
替换整个表达式,同时删除 if
和 do_stuff()
.
一般情况下,编译器特别不允许对&&
和||
的操作数的求值或执行重新排序;它们在左操作数和右操作数的计算之间有一个所谓的序列点。这反过来又允许像 (ptr=malloc(...)) && (*ptr = x)
这样的代码被很好地定义并且在 malloc
失败时不会访问空指针。