1

As far as I'm aware of, unary operators have prior precedence over || and &&. In the following code, I'd expect an output where all outputs are equal to 1. Yes, there is a short-circuit but shouldn't those pre-increments calculated before || and &&? How these precedences work here?

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a = 0, b = 0, c = 0;
    a = b = c == 1;
    c = ++a || ++b && ++c;    // short-circuit here
    printf("a=%d, b=%d, c=%d\n", a, b, c);
}

Output:
a=1 b=0 c=1
Alex Riley
  • 169,130
  • 45
  • 262
  • 238
Motun
  • 2,149
  • 3
  • 16
  • 23
  • 2
    Did you really mean `a = b = c == 1;`? – erip Dec 06 '15 at 21:12
  • 1
    You're mixing up "precedence" with "order of evaluation". "precedence" tells us that it means `(++a) || ((++b) && (++c))`, and not for example `++( (a || (++b)) && ++c)` , but says nothing more specific about which order those operations are executed. – M.M Dec 06 '15 at 21:15
  • @juanchopanza: Actually the second operand **must not** be evaluated if the result is determined by the first operand already. It is not just left to the compiler. – too honest for this site Dec 06 '15 at 21:24
  • @Olaf I didn't say that. It is determined by the value of the operands, and the nature of the operator. – juanchopanza Dec 06 '15 at 21:25
  • Note that if `a` starts off as `-1` (so that `++c` gets evaluated), that line causes undefined behaviour as there are no sequence points between `++c` and `c =` (the sequence points from the || && are strictly before `++c`). To avoid this issue you could use a different variable for the result. Theoretically a compiler could notice this and then assume `a != -1` – M.M Dec 06 '15 at 21:26
  • @juanchopanza: Isn't "do not have to" weaker than `must not`? Anyway, the second operand must be evaluated if required. – too honest for this site Dec 06 '15 at 21:26
  • @erip Yes, `a` and `b` assigned like that. – Motun Dec 06 '15 at 21:27
  • 2
    `a = b = c == 1;` is a slightly odd line (this actually has no effect) – M.M Dec 06 '15 at 21:27
  • @Olaf must not isn't accurate, because the operands may be evaluated. Depending on their value and the nature of the operator. Must not suggests they are never evaluated. – juanchopanza Dec 06 '15 at 21:28
  • @juanchopanza the way you worded your comment, it looks like you are suggesting that even if the short-circuiting occurs, the later operands might be evaluated anyway – M.M Dec 06 '15 at 21:29
  • @juanchopanza: I think I wrote that very clear: " ... must not be evaluated **if** ... " – too honest for this site Dec 06 '15 at 21:29
  • @M.M: Thanks, that's what I mean. – too honest for this site Dec 06 '15 at 21:30
  • @M.M Fine, I'll remove it. I got confused by OP claiming they understand short circuit, but obviously that isn't the case. – juanchopanza Dec 06 '15 at 21:33
  • @erip Right, I was mixing them. So can we conclude that "Precedence makes those term grouped but doesn't guarantee that they will be evaluated before all others."? – Motun Dec 06 '15 at 21:35
  • @erip you just restated the misconception. Precedence determines the grouping of terms, but not what happens first (other than the minimum required by that particular grouping) – M.M Dec 06 '15 at 22:01
  • Trying `c = ++b && ++c || ++a;` instead of `c = ++a || ++b && ++c;` makes it clearer. – Motun Dec 06 '15 at 22:05

3 Answers3

3

Operator precedence doesn't have any relation with order of evaluation. Higher precedence means that grouping of operands to that operator is done first.

In the statement

c = ++a || ++b && ++c;  

the grouping/binding of the operands of ++ will be done first and then that of && and || respectively. To show this I am adding parenthesis to the expression

  • ++ has higher precedence so bind operand to it first

    c = (++a) || (++b) && (++c); 
    
  • && has higher precedence than ||

    c = (++a) || ((++b) && (++c)); 
    
  • || has higher precedence than =

    c = ((++a) || ((++b) && (++c)));   
    
  • = has least precedence of all

    (c = ((++a) || ((++b) && (++c))));   
    
haccks
  • 104,019
  • 25
  • 176
  • 264
2

The fact that || is short-circuiting and the lowest precedence operator explains the result. Because ++ is the higher precedence than && and && is higher precedence than ||, the expression tree for ++a || ++b && ++c is:

       ||   -- left:   ++a
            -- right:  &&   --left:  ++b
                            --right: ++c

So, to evaluate the expression, C first considers the rules for evaluating ||, given by 6.5.14 in the C11 standard. Specifically:

6.5.14.4: Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares unequal to 0, the second operand is not evaluated.

The fact that || is short-circuiting means that it evaluates its left operand first and only evaluates its right operand if the left is zero. So to evaluate the expression ++a || ++b && ++c, C requires the following:

  1. Evaluate ++a (a is incremented by one and the expression is equal to its incremented value). If it is non-zero, then || expression is equal to 1 and the right side is never evaluated.
  2. Otherwise, evaluate ++b and ++c, in left-to-right order. If ++b is zero, then ++c is never evaluated. If both are non-zero, then the expression is equal to 1.

Because ++a evaluates to 1, the right side of the || is never evaluated. This is why you have a==1, b==0, and c==1.

There's one additional problem with the statement c = ++a || ++b && ++c, which is that there's a potential for the statement to invoke undefined behavior. If ++a is false and ++b is true, then ++c has to be evaluated. However, there is no sequence point between c = ... and ++c. Since the expressions both modify c with no sequence point in between, the behavior is undefined. For a further explanation of this see, for example, https://stackoverflow.com/a/3575375/1430833

Community
  • 1
  • 1
sfstewman
  • 5,589
  • 1
  • 19
  • 26
  • Thanks a lot for the reference. That's why I'm choosing this as the answer. – Motun Dec 06 '15 at 21:45
  • You've not mentioned that the expression as a whole has the potential for undefined behaviour because `c = … && ++c` modifies and uses `c` between sequence points (the sequence point after the LHS of `&&` is evaluated and the end of statement). GCC points that out (`warning: operation on ‘c’ may be undefined [-Wsequence-point]`). In this case, because of the values involved, it is OK; the `c++` is never executed and `c` acquires the value `1` from the fact that the expression as a whole evaluates to `1` (true) because `++a` is non-zero. – Jonathan Leffler Dec 06 '15 at 22:13
  • You also say "evaluate `++b` and `++c`, in no guaranteed order", but the order of evaluation is guaranteed — the `&&` and `||` operators guarantee that their LHS is fully evaluated before the RHS is evaluated, including side-effects, because there is a full sequence point between evaluation of the LHS and the evaluation of the RHS. – Jonathan Leffler Dec 06 '15 at 22:18
  • @JonathanLeffler You're right about `&&`. That was careless of me. I thought it would cloud the issue to point out that there's a sequence point issue with `++c` since that will never arise in the OP's code, but that's certainly true. – sfstewman Dec 06 '15 at 22:50
  • If `++a` and `++b` are both false, will `++c` be evaluated? I think `++b && ++c` would be directly evaluated to `false`. – Motun Dec 06 '15 at 23:29
  • @Motun, you're absolutely right. I was short on time when I made that edit, and `++c` is only evaluated when `++a` is false and `++b` is true. I've fixed the answer. – sfstewman Dec 06 '15 at 23:37
1

I think you're confusing operator precedence with order of evaluation. Here's an outline of what happens. In the comments, my use of = is that of identity - much like the mathematical equals sign.

#include <stdio.h>
#include <stdlib.h>

int main()
{
    // all variables are assigned the value 0
    int a = 0, b = 0, c = 0;

    // a and b are equal to the evaluation of `c == 1`, which is false. a and b are 0
    a = b = c == 1;

    /* 
       && has higher precedence than ||, so ++b and ++c are "grouped": ++a || (++b && ++c)
       ++a is evaluated, a = 0+1 = 1. 1 is true, short circuit the second grouping. 
       c = a = 1.
    */
    c = ++a || ++b && ++c;    // short-circuit here

    // a = c = 1, b = 0
    printf("a=%d, b=%d, c=%d\n", a, b, c);
}
erip
  • 16,374
  • 11
  • 66
  • 121