为啥当你将函数的返回值乘以零时它不会短路?

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

【问题讨论】:

该语言要求在应用操作之前评估所有操作数。只有逻辑运算符(&amp;&amp;||)在 C++ 中执行短路。 如果您删除打印,您是否检查了为 main 生成的优化代码? 【参考方案1】:

它不是从左到右计算,看到 0 * (unsigned int) 并知道右值为 0?

它可以,而且如果标准要求它这样做。

但事实并非如此。

短路根本不适合乘法。他们本可以把它变成一件事,但它可能会令人困惑。

我们都习惯了f() || g() 可能会跳过对g() 的调用,但你真的希望0 * g() 做同样的事情吗?特别是因为0 只是十亿个可能的整数之一?这将是一个奇怪的特定功能。 (相比之下,true 是仅有的两个布尔值之一。)

这与副作用无关,因为如果f() 返回truef() || g() 将跳过g() 的副作用。就是这样。

在实践中,如果编译器知道g() 没有副作用(因此程序的行为不会改变),编译器可以0 * g() 中忽略对g() 的调用,但这不是短路;那是"optimisation"。

【讨论】:

当我们谈论乘法时,0 不仅仅是十亿个整数之一。它与 false 对逻辑与的结构相同,true 对或的结构相同。 @xtofl “相同结构”是什么意思? “感觉”就像零是乘以 false 是结合。我的代数不够好,无法更好地表述。 我想我能明白你的意思【参考方案2】:

&amp;&amp;(逻辑)、||(逻辑)和?(三元运算符)必须进行短路评估。对于其余的运算符,这是一个(可选的)优化。

表达式0 * fact(5)fact(5) 的评估通常不能仅仅因为您知道整个表达式的结果是0 而被优化掉,因为对fact() 的调用可能会引入副作用(例如,修改一些全局变量),所以必须调用它。

正如this comment 中所说,如果可以证明没有副作用,好的编译器会优化对fact(5) 的调用。

【讨论】:

一个好的编译器忽略对fact的调用,如果它可以证明它没有副作用。例如,如果删除了cout,则 GCC does so 是 OP 的示例。 关于副作用的讨论有点让人分心,因为无论副作用如何,短路的||/&amp;&amp; 操作数都可以省略。 @LightnessRacesBY-SA3.0 谢谢,我已经相应地编辑了答案。

以上是关于为啥当你将函数的返回值乘以零时它不会短路?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个函数返回垃圾值

为啥函数没有返回值? [关闭]

为啥 main() 函数不返回浮点值?

为啥函数返回的值总是 null 或 undefined?

该函数应返回字符串乘以整数[关闭]

为啥在 Laravel 中使用 PHPUnit 的函数“link_to”没有返回值?