为什么C中的逻辑运算符在没有必要的时候不会评估整个表达式?
我正在阅读我的计算机体系结构课程的教科书,我发现了这个声明。
逻辑运算符’
&&
‘和’||
之间的第二个重要区别 ‘与他们的比特级对手’&
‘和’|
‘如果可以通过计算第一个参数来确定表达式的结果,那么逻辑运算符不会计算它们的第二个参数。 因此,例如,表达式a && 5/a
将永远不会导致除零,而表达式p && *p++
将永远不会导致空指针的解除引用。 (计算机系统:由Bryant和O’Hallaron撰写的程序员观点,第3版,第57页)
我的问题是为什么C中的逻辑运算符表现如此? 使用作者的a && 5/a
的例子,C不需要评估整个表达式,因为&&
要求两个谓词都是真的吗? 不失一般性,我的同样问题适用于他的第二个例子。
短路是一种性能增强,恰好可用于其他目的。
你说“不会C需要评估整个表达式,因为&&
要求两个谓词都是真的吗?” 但想一想。 如果&&
的左侧是假的,那么右侧评估的内容是否重要? false && true
或false && 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
保护自己的东西。
偶尔如果你总是需要评估两个参数(也许它们调用具有副作用的函数),你总是可以使用&
和|
。