0

So I was reading about the order of different operators, and I read that && has higher importance than || and it would evaluate sooner (source). Then somebody asked a question about what this piece of code would print:

#include <stdio.h>
int main(){
    int a=0, b=0, c=0, d=0;
    if(a++>0 || ++b==1 || c--<=0 && d++>c--){
        printf("if\na:%d\nb:%d\nc:%d\nd:%d\n",a,b,c,d);
    }
    else{
        printf("else\na:%d\nb:%d\nc:%d\nd:%d\n",a,b,c,d);
    }
    return 0;
}

And I thought that the c-- <= 0 && d++ > c-- would evaluate first, which is true in total. after the process, c would be equal to -2 and d would be equal to 1. Then it would start checking from the left side, evaluating a++ > 0 || ++b == 1 which is true, a would be 1 at the end and b is 1 in the condition and after that. so the total condition would be true || true and it is true, so we will print:

if
a:1
b:1
c:-2
d:1

Yes? Apparently, no. I've tested it with GCC (Mingw) on my system (Windows 10), and with an online compiler (this one) and both printed:

if
a:1
b:1
c:0
d:0

I've changed the condition into this: if(a++>0 || ++b==1 || (c--<=0 && d++>c--) ) but the output is the exact same thing on both places. Is there something that I don't pay attention to? Or is this something like a bug? It almost looks like that || and && have the same priority and the whole thing is evaluated from the left side, and short-circuits occurs and other things. If I change the ++b==1 part into ++b==0, then the output is the same as I predicted.
Thanks in advance for any kind of help :)

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
III_phr
  • 52
  • 6
  • TL;DR, but sounds that you might be interested in learning about *short-circuit evaluation* – Eugene Sh. Nov 23 '21 at 17:53
  • @EugeneSh. Yeah, I know something about that. But isn't the order important in that? I mean, what is the point of the order of importance, if it would be evaluated from the left all the time (which I know it does not)? – III_phr Nov 23 '21 at 17:55
  • Briefly, precedence tells us how the compiler will wrap a chain of operations in `()` to remove ambiguity. It's _not_ the same thing as evaluation order. So, your explicitly-parenthesized version is the same thing the compiler did already according to the precedence rules. – Useless Nov 23 '21 at 17:57
  • @Useless But both seem wrong to me. Because the `&&` has higher order and must be evaluated first. Am I wrong? – III_phr Nov 23 '21 at 17:58
  • In your both conditions the evaluation is stopped at `++b==1` . Anything on the right of it is not being evaluated, parenthesized or not. – Eugene Sh. Nov 23 '21 at 17:58
  • 1
    Particularly see the note [here](https://en.cppreference.com/w/c/language/operator_precedence): _Precedence and associativity are independent from order of evaluation_ (and follow the link to understand order of evaluation) – Useless Nov 23 '21 at 17:59
  • *Because the && has higher order and must be evaluated first* - it would be evaluated if it **needs** to be evaluated. But it does not according to short-circuiting. – Eugene Sh. Nov 23 '21 at 17:59
  • Thank you both, I'll read the note now. – III_phr Nov 23 '21 at 18:00
  • Your source is wrong, notably in the sentence “Expressions with higher-precedence operators are evaluated first.” It is also wrong to say “The direction of evaluation does not affect the results of expressions that include more than one multiplication…”, as floating-point arithmetic is not associative… – Eric Postpischil Nov 23 '21 at 18:00
  • … It is also wrong in “evaluation proceeds according to the associativity of the operator, either from right to left or from left to right” because operands may contain subexpressions with side effects, and those subexpressions may be evaluated in diverse orders; the full tree under the expression E2 in “E1 * E2” may be evaluated before any part of the subexpressions in E1. Stop using that source. – Eric Postpischil Nov 23 '21 at 18:01
  • @EricPostpischil I really didn't know that it can be wrong. Thanks for the information. I just searched for the order of evaluation and it was one of the first things that google showed. – III_phr Nov 23 '21 at 18:02
  • Try the one I linked instead, it's generally reliable. [Relevant link](https://en.cppreference.com/w/c/language/operator_logical). I try to ignore Microsoft documentation as much as possible, they have (or used to have) a terrible reputation for standards conformance. – Useless Nov 23 '21 at 18:03
  • @Useless yeah, I read it right now. So there is no way to actually and precisely tell which of the expressions would evaluate first? – III_phr Nov 23 '21 at 18:04
  • You have to be careful asking about which things "evaluate first". It can be a slippery, meaningless concept. For the `&&` and `||` operators (but pretty much only for these operators), you can — sort of — assume a reasonaby pure left-to-right evaluation. – Steve Summit Nov 23 '21 at 18:05
  • The section on short-circuit evaluation in that logical operators page I linked (and indeed the linked duplicate question) address this: these are a specific special case where you know both the exact evaluation order (left to right), and that evaluation can stop early. – Useless Nov 23 '21 at 18:06
  • If you write `f() || g()` or `x() && y()` you can be sure that `f` will be called before `g`, and that `x` will be called before `y`. But if you say `f() + g()` or `x() - y()`, you *can not tell* in which order the functions will be called. – Steve Summit Nov 23 '21 at 18:08
  • See also [this answer](https://stackoverflow.com/questions/31087537/why-does-a-b-have-the-same-behavior-as-a-b/31088592#31088592). – Steve Summit Nov 23 '21 at 18:10
  • 1
    Bottom line: saying that "higher-precedence operators are evaluated first" is a very very common, but potentially very misleading, thing to say. – Steve Summit Nov 23 '21 at 18:11
  • I read every link you all mentioned here now. I think it is more clear now for me. Thanks for the kind comments and the priceless information :) – III_phr Nov 23 '21 at 18:13
  • See also [Is short-circuiting logical operators mandated? And evaluation order?](https://stackoverflow.com/questions/628526) – Steve Summit Nov 24 '21 at 00:28

1 Answers1

4

The expression in this question:

if(a++>0 || ++b==1 || c--<=0 && d++>c--)

is a classic example of a horrible, horrible expression, outrageously unrealistic and impractical, and punishingly difficult to understand, which nevertheless does a good job of illustrating a super important point: precedence is not the same as order of evaluation.

What precedence really tells us is how the operators are hooked up with their operands. So given the simplified expression

A || B || C && D

which two subexpressions do the first ||, and the second ||, and the && actually tie together and operate on? If you're a compiler writer, you answer these questions by constructing a "parse tree" which explicitly shows which subexpression(s) go with which operators.

So, given the expression A || B || C && D, does the parse tree for the expression look like this:

        &&
       /  \
     ||    D
    /  \
  ||    C
 /  \
A    B

or like this:

  ||
 /  \
A    ||
    /  \
   B    &&
       /  \
      C    D

or like this:

      ||
     /  \
    /    \
  ||      &&
 /  \    /  \
A    B  C    D

To answer this, we need to know not only that the precedence of && is higher than ||, but also that || is left-associative. Given these facts, the expression

A || B || C && D

is parsed as if it had been written

(A || B) || (C && D)

and, therefore, results in the third of the three candidate parse trees I showed:

      ||
     /  \
    /    \
  ||      &&
 /  \    /  \
A    B  C    D

But now we're in a position to really see how the "short circuiting" behavior of the || and && operators is going to be applied. That "top" || is going to evaluate its left-hand side and then, if it's false, also evaluate the right-hand side. Similarly, the lower || is going to evaluate its left-hand side. So, no matter what, A is going to get evaluated first. For the expression in the original question, that corresponds to a++ > 0.

Now, a++>0 is false, so we're going to have to evaluate B, which is ++b == 1. Now, that is true, so the result of the first || is "true".

So the result of the second (top) || operator is also "true".

So the right-hand side of the top || operator does not have to be evaluated at all.

So the entire subexpression containing && will not be evaluated at all.

So even though && had the highest precedence, it ended up getting considered last, and (since the stuff to the left involved || and was true) it did not end up getting evaluated at all.

The bottom line, as I started out by saying, is that precedence does not determine order of evaluation.

Also, if it wasn't said elsewhere, this guaranteed, left-to-right behavior is only guaranteed for the || and && operators (and, in a different way, for the ternary ?: operator). If the expression had been

A + B + C * D

it would not have been true that, as I said earlier, "no matter what, A is going to get evaluated first". For arithmetic operators like + and *, there's no way to know whether the left-hand side or the right-hand side is going to get evaluated first.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103