为何乘法未像布尔表达式那样实现短路优化?
先看几个对比场景:
布尔逻辑的短路优化能大幅减少递归调用次数:
bool foo(u_int d){ if (d > 1) return foo(d - 1) || foo(d - 2); return true; }
因为||的短路特性,只要foo(d-1)返回true,右侧的foo(d-2)就不会执行,复杂度直接从O(Fₙ)(斐波那契级)降到O(d)。同理,&&在左侧为false时也会跳过右侧执行。
但换成乘法就完全不一样:
int bar(u_int d){ if (d > 1) return 0 * bar(d - 1) * bar(d - 2); return 1; }
哪怕第一个因子是0,后面的bar(d-1)和bar(d-2)还是会被挨个执行,导致斐波那契级的递归调用;甚至像return 0 * bar();这种代码,会直接触发无限递归。
为什么乘法不做类似的短路优化?核心原因大概有这么几个:
副作用的不可控性:布尔短路运算符(
||/&&)的语义是语言明确规定的,程序员写的时候就清楚“可能跳过右侧代码”。但乘法的操作数经常带有副作用——比如0 * printf("hello"),如果短路的话,printf就不会执行,这完全违背了写代码时的预期。逻辑运算的副作用往往是程序员有意利用的(比如ptr != NULL && ptr->val避免空指针),但乘法没人会依赖“跳过右侧”的行为,反而默认所有操作数都会被求值。逻辑特性与语言设计的本质差异:逻辑或/与的短路是逻辑运算本身的必然要求——逻辑或只要一个为真结果就确定,逻辑与只要一个为假结果就确定,这是逻辑规则的一部分。但乘法是算术运算,语言设计时从来没把“跳过后续操作数”作为它的语义。强行加短路会打破算术运算的常规行为,让语言规则变得混乱不一致。
求值顺序的冲突:在多数编程语言里,除了短路运算符,普通表达式的操作数求值顺序是不做保证的(比如C语言里,编译器可以任意顺序求值
a * b * c的三个操作数)。如果乘法支持短路,就必须强制先求值左侧操作数,确定是0后再跳过右侧,这会和语言原有的求值规则冲突,既增加编译器实现的复杂度,也让代码行为更难预测。优化场景的局限性:编译器确实会做算术优化,比如把
0 * 100这种常量表达式直接折叠成0,但针对运行时才能确定的0(比如变量x * bar(),x的值只有运行时才知道),短路优化的收益有限,反而会带来一堆边界问题——比如浮点数的0.0和-0.0,或者遇到NaN的情况,这些都会让短路逻辑变得异常复杂,完全得不偿失。
内容的提问来源于stack exchange,提问作者Unnamed

