为什么C中的逻辑运算符在没有必要的时候不会评估整个表达式?

我正在阅读我的计算机体系结构课程的教科书,我发现了这个声明。

逻辑运算符’ && ‘和’ ||之间的第二个重要区别 ‘与他们的比特级对手’ & ‘和’ | ‘如果可以通过计算第一个参数来确定表达式的结果,那么逻辑运算符不会计算它们的第二个参数。 因此,例如,表达式a && 5/a将永远不会导致除零,而表达式p && *p++将永远不会导致空指针的解除引用。 (计算机系统:由Bryant和O’Hallaron撰写的程序员观点,第3版,第57页)

我的问题是为什么C中的逻辑运算符表现如此? 使用作者的a && 5/a的例子,C不需要评估整个表达式,因为&&要求两个谓词都是真的吗? 不失一般性,我的同样问题适用于他的第二个例子。

短路是一种性能增强,恰好可用于其他目的。

你说“不会C需要评估整个表达式,因为&&要求两个谓词都是真的吗?” 但想一想。 如果&&的左侧是假的,那么右侧评估的内容是否重要? false && truefalse && false ,结果相同:false。

因此,当&&的左侧确定为假,或者左侧是|| 确定为真,右边的值无关紧要,可以跳过。 通过消除评估可能昂贵的第二次测试的需要,这使代码更快。 想象一下,如果右侧调用一个函数来扫描整个文件中的给定字符串? 如果第一次测试意味着您已经知道合并后的答案,您是不是希望跳过该测试?

C决定超越保证短路以保证评估顺序,因为这意味着您提供的安全测试是可能的。 只要测试是幂等的,或者只是在没有短路的情况下发生副作用,这个特征是可取的。

一个典型的例子是空指针检查:

 if(ptr != NULL && ptr->value) { .... } 

如果没有短路评估,当空指针被解除引用时,这将导致错误。

程序首先检查左侧部分ptr != NULL 。 如果此计算结果为false ,则不必评估第二部分,因为已经清楚结果将为false

在表达式X && Y ,如果X被评估为false ,那么我们知道X && Y将始终为false ,无论Y的值是什么。 因此,无需评估Y

在您的示例中使用此技巧以避免除以0 。 如果a被评估为false (即a == 0 ),那么我们不评估5/a

它还可以节省大量时间。 例如,在评估f() && g() ,如果对g()的调用很昂贵并且如果f()返回false ,则不评估g()是一个很好的特性。

不会C需要评估整个表达式,因为&&要求两个谓词都是真的吗?

答: 不可以。 当“已经”知道答案时,为什么要更多地工作?

根据逻辑AND运算符的定义,引用C11 ,章节§6.5.14

如果两个操作数的比较不等于0, &&运算符将产生1 ; 否则,它产生0。

在该类比之后,对于formsa && b的表达式,在计算结果为FALSE的情况下,不管b的评估结果如何,结果将为FALSE。 无论如何,无需浪费机器周期检查b然后返回FALSE。

对于逻辑OR运算符也是如此,如果第一个参数的计算结果为TRUE,则已找到返回值条件,并且无需计算第二个参数。

这只是规则,非常有用。 也许这就是为什么这是规则。 这意味着我们可以编写更清晰的代码。 另一种方法 – 使用if语句会产生更冗长的代码,因为你不能直接在表达式中使用if语句。

你已经举了一个例子。 另一个是if (a && b / a)来防止整数除零,其行为在C中是未定义的。 就是作者在编写a && 5/a保护自己东西。

偶尔如果你总是需要评估两个参数(也许它们调用具有副作用的函数),你总是可以使用&|