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

时间:2016-09-21 10:16:18

标签: c logical-operators short-circuiting

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

  

逻辑运算符'&&'和'||'与其位级对应'&'和'|'之间的第二个重要区别是如果可以通过计算第一个参数来确定表达式的结果,则逻辑运算符不会评估它们的第二个参数。因此,例如,表达式a && 5/a将永远不会导致除以零,并且表达式p && *p++将永远不会导致空指针的解除引用。 (计算机系统:由Bryant和O'Hallaron撰写的程序员视角,第3版,第57页)

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

5 个答案:

答案 0 :(得分:2)

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

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

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

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

答案 1 :(得分:1)

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

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

如果没有短路评估,这将在取消引用空指针时导致错误。

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

  

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

答案:否。 为什么在“已经”知道答案时工作得更多?

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

  

如果&&运算符的两个操作数都不等于0,则1运算符将产生a && b ;否则,它   产量为0.

按照这个类比,对于a形式的表达式,如果b求值为FALSE,则无论b的求值结果如何,结果都将为FALSE。无论如何,无需浪费机器周期检查function clickStart(){ document.write("<input type='button' value='attack!' onClick='clickAttack();' /"); 然后返回FALSE。

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

答案 4 :(得分:0)

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

你已经举了一个例子。另一个类似于if (a && b / a)来防止整数除零,其行为在C中是 undefined 那是作者正在守护着自己从写作a && 5/a

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