简体   繁体   中英

Why do logical operators in C not evaluate the entire expression when it's not necessary to?

I was reading my textbook for my computer architecture class and I came across this statement.

A second important distinction between the logical operators ' && ' and ' ||' versus their bit-level counterparts ' & ' and ' | ' is that the logical operators do not evaluate their second argument if the result of the expression can be determined by evaluating the first argument. Thus, for example, the expression a && 5/a will never cause a division by zero, and the expression p && *p++ will never cause the dereferencing of a null pointer. (Computer Systems: A Programmer's Perspective by Bryant and O'Hallaron, 3rd Edition, p. 57)

My question is why do logical operators in C behave like that? Using the author's example of a && 5/a , wouldn't C need to evaluate the whole expression because && requires both predicates to be true? Without loss of generality, my same question applies to his second example.

Short-circuiting is a performance enhancement that happens to be useful for other purposes.

You say "wouldn't C need to evaluate the whole expression because && requires both predicates to be true?" But think about it. If the left hand side of the && is false, does it matter what the right hand side evaluates to? false && true or false && false , the result is the same: false.

So when the left hand side of an && is determined to be false, or the left hand side of a ||is determined to be true, the value on the right doesn't matter, and can be skipped. This makes the code faster by removing the need to evaluate a potentially expensive second test. Imagine if the right-hand side called a function that scanned a whole file for a given string? Wouldn't you want that test skipped if the first test meant you already knew the combined answer?

C decided to go beyond guaranteeing short-circuiting to guaranteeing order of evaluation because it means safety tests like the one you provide are possible. As long as the tests are idempotent, or the side-effects are intended to occur only when not short-circuited, this feature is desirable.

A typical example is a null-pointer check:

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

Without short-circuit-evaluation, this would cause an error when the null-pointer is dereferenced.

The program first checks the left part ptr != NULL . If this evaluates to false , it does not have to evaluate the second part, because it is already clear that the result will be false .

In the expression X && Y , if X is evaluated to false , then we know that X && Y will always be false , whatever is the value of Y . Therefore, there is no need to evaluate Y .

This trick is used in your example to avoid a division by 0 . If a is evaluated to false (ie a == 0 ), then we do not evaluate 5/a .

It can also save a lot of time. For instance, when evaluating f() && g() , if the call to g() is expensive and if f() returns false , not evaluating g() is a nice feature.

wouldn't C need to evaluate the whole expression because && requires both predicates to be true?

Answer: No. Why work more when the answer is known "already"?

As per the definition of the logical AND operator, quoting C11 , chapter §6.5.14

The && operator shall yield 1 if both of its operands compare unequal to 0; otherwise, it yields 0.

Following that analogy, for an expression of the form a && b , in case a evaluates to FALSE, irrespective of the evaluated result of b , the result will be FALSE. No need to waste machine cycle checking the b and then returning FALSE, anyway.

Same goes for logical OR operator, too, in case the first argument evaluates to TRUE, the return value condition is already found, and no need to evaluate the second argument.

It's just the rule and is extremely useful. Perhaps that's why it's the rule. It means we can write clearer code. An alternative - using if statements would produce much more verbose code since you can't use if statements directly within expressions .

You already give one example. Another is something like if (a && b / a) to prevent integer division by zero, the behaviour of which is undefined in C. That's what the author is guarding themselves from in writing a && 5/a .

Very occasionally if you do always need both arguments evaluated (perhaps they call functions with side effects), you can always use & and | .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM