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 :)
CodePudding user response:
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 illustrates 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 building 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.