为啥当你将函数的返回值乘以零时它不会短路?
Posted
技术标签:
【中文标题】为啥当你将函数的返回值乘以零时它不会短路?【英文标题】:Why doesn't it short-circuit when you multiply the return value of a function by zero?为什么当你将函数的返回值乘以零时它不会短路? 【发布时间】:2020-04-17 15:19:42 【问题描述】:考虑如下函数:
unsigned int fact(unsigned int i)
if (i <= 1) return 1;
return i * fact(i-1);
如果我要实例化一个新变量unsigned int f
这样f = 0 * fact(5)
,为什么它不会“短路”?
unsigned int fact(unsigned int i)
std::cout << "a";
if (i <= 1) return 1;
return i * fact(i-1);
int main()
unsigned int f = 0 * fact(5);
这里的输出是aaaaa
。如果f
只能为零,假设它知道返回类型,为什么还要调用该函数?它不是从左到右评估吗,请参阅0 * (unsigned int)
并知道右值将是0
?
【问题讨论】:
该语言要求在应用操作之前评估所有操作数。只有逻辑运算符(&&
和 ||
)在 C++ 中执行短路。
如果您删除打印,您是否检查了为 main 生成的优化代码?
【参考方案1】:
它不是从左到右计算,看到 0 * (unsigned int) 并知道右值为 0?
它可以,而且如果标准要求它这样做。
但事实并非如此。
短路根本不适合乘法。他们本可以把它变成一件事,但它可能会令人困惑。
我们都习惯了f() || g()
可能会跳过对g()
的调用,但你真的希望0 * g()
做同样的事情吗?特别是因为0
只是十亿个可能的整数之一?这将是一个奇怪的特定功能。 (相比之下,true
是仅有的两个布尔值之一。)
这与副作用无关,因为如果f()
返回true
,f() || g()
将跳过g()
的副作用。就是这样。
在实践中,如果编译器知道g()
没有副作用(因此程序的行为不会改变),编译器可以在0 * g()
中忽略对g()
的调用,但这不是短路;那是"optimisation"。
【讨论】:
当我们谈论乘法时,0 不仅仅是十亿个整数之一。它与false
对逻辑与的结构相同,true
对或的结构相同。
@xtofl “相同结构”是什么意思?
“感觉”就像零是乘以 false
是结合。我的代数不够好,无法更好地表述。
我想我能明白你的意思【参考方案2】:
&&
(逻辑和)、||
(逻辑或)和?
(三元运算符)必须进行短路评估。对于其余的运算符,这是一个(可选的)优化。
表达式0 * fact(5)
中fact(5)
的评估通常不能仅仅因为您知道整个表达式的结果是0
而被优化掉,因为对fact()
的调用可能会引入副作用(例如,修改一些全局变量),所以必须调用它。
正如this comment 中所说,如果可以证明没有副作用,好的编译器会优化对fact(5)
的调用。
【讨论】:
一个好的编译器将忽略对fact
的调用,如果它可以证明它没有副作用。例如,如果删除了cout
,则 GCC does so 是 OP 的示例。
关于副作用的讨论有点让人分心,因为无论副作用如何,短路的||
/&&
操作数都可以省略。
@LightnessRacesBY-SA3.0 谢谢,我已经相应地编辑了答案。以上是关于为啥当你将函数的返回值乘以零时它不会短路?的主要内容,如果未能解决你的问题,请参考以下文章