据我所知,C对逻辑表达式使用延迟计算,例如: G。在表达
f(x) && g(x)
如果g(x)
为假,则不会调用 f(x)
。
但算术表达式如
呢?f(x)*g(x)
如果g(x)
为零,是否会调用f(x)
?
答案 0 :(得分:3)
是的,算术运算是急切的,而不是懒惰的。
所以在f(x)*g(x)
中总是调用f
和g
(迂腐地,编译器正在将其转换为某些A-normal form,如果不可观察,甚至可以避免一些调用),但无法保证在f
之前或之后调用g
的顺序。当x*1/x
为0时,评估y*1/x
或x
为undefined behavior。
在Haskell AFAIU中并非如此
答案 1 :(得分:2)
是的,g(x)
仍然会被调用。
一般来说,仅仅因为左侧是零,有条件地忽略右侧的评估是非常缓慢的。也许不是在右侧是昂贵的函数调用的情况下,但编译器不会假设知道这一点。
答案 2 :(得分:2)
它叫做"Short Circuit"而不是懒惰。并且,至少就标准问题而言,是 - 即,它没有指定*
的短路评估。
如果可以确定g()
没有副作用,编译器可能能够进行短路评估,但只能在as-if规则下进行(即,它只能通过发现没有副作用来实现外部可观察到的差异,不是因为标准给予它任何直接许可这样做。)
答案 3 :(得分:1)
如果逻辑运算符&&
和||
评估顺序从左到右发生,短路发生。
在评估&&
(逻辑AND),||
(逻辑OR)的左右操作数之间存在序列点(作为短路评估的一部分)。例如,在表达式*p++ != 0 && *q++ != 0
中,子表达式*p++ != 0
的所有副作用都在尝试访问q
之前完成,但在算术运算符的情况下则不然。
答案 4 :(得分:1)
虽然可以进行优化,但有一些反对意见:
您可能会为优化支付的费用高于您从中获得的优惠:与逻辑运算符不同,优化可能只对所有具有算术运算符的情况的一小部分有益,但同时需要为每个操作额外检查0。
因为布尔值真值只有两个可能的值,所以第二个操作数不需要的短路布尔表达式有theoretical 50%几率(1÷2)被评估。 (这假设均匀分布,这可能不太现实,但请耐心等待。)也就是说,您可能会在相对较大比例的情况下从优化中获利。
将此与整数进行对比,其中0只是数百万中可能值中的一个。第一个操作数为0的概率要低得多:1÷2 32 (对于32位整数,再次假设均匀分布)。即使0实际上发生的可能性稍大(即分布不均匀),我们仍然不可能处理与真值相同的数量级。
浮点数学进一步加剧了这个问题。在这里,您需要处理舍入错误和非规范化的可能性。某些计算产生正好 0的概率可能甚至低于整数。
因此,优化相对不太可能导致剩余的操作数未被评估。但将导致100%的额外检查为零!
如果您希望评估规则保持合理一致,则必须重新定义&&
和||
的短路评估顺序:部门有一个重要的极区情况,即除以0:即使第一个操作数为0,商也不一定为0。除以0之外的部分将被视为错误(可能在IEEE浮点数学中除外);因此,您始终必须评估第二个操作数,以确定计算是否有效。
/
有一个替代优化:除以1.在这种情况下,你根本不需要除法,而只是返回第一个操作数。因此,从第二个操作数(除数)开始,可以更好地优化/
。
现在,除非您希望&&
,||
和*
开始使用第一个操作数进行评估,但/
要从第二个操作数开始(这可能看起来不直观) ),你必须通常重新定义短路行为,以便首先评估第二个操作数总是,这将偏离现状。
这本身不是问题,但如果C语言发生了变化,可能会破坏很多现有代码。
优化可能会破坏与C ++代码的“兼容性”,其中运算符可能会过载。优化是否仍适用于重载的*
和/
运算符?或者是否必须有两种不同形式的这些操作符,一种是短路的,另一种是急切的评估?
同样,这不是短路算术运算符固有的缺陷,但如果将这种短路作为一种重大改变引入C(和C ++)语言,就会出现这个问题。