开发者

&& and || operators [duplicate]

开发者 https://www.devze.com 2023-04-12 03:55 出处:网络
This question already has answers here: Why does "++x || ++y && ++z" calculate "++x" first, even though operator "&&" has higher precedence than &q开发
This question already has answers here: Why does "++x || ++y && ++z" calculate "++x" first, even though operator "&&" has higher precedence than &q开发者_如何学Pythonuot;||" (11 answers) Closed 4 years ago.

I came across this code:

    int main()
    {
        int i=1,j=2,k=0,m=0;
        m = ++i || ++j && ++k;
        printf("%d %d %d %d %d",i,j,k,m);
    }

The program returns 2 2 0 1.... Why?

&& has a higher priority than || so ++j && ++k should be evaluated first. Hence I would expect j=3 and k=1. It will return true hence || becomes true so ++i shouldn't be evaluated. But it works other way around.

I would like others to explain to me.


Having higher precedence does not mean it gets evaluated first. It just means it binds tighter. In that example, that expression is equivalent to: ++i || (++j && ++k). What gets evaluated first is ++i because || evaluates left to right. Only if that evaluates to false will ++j && ++k be evaluated because || is short-circuiting.


Actually ++i will be evaluated first. Only if it's false will the right side be evaluated (and in your case it's not).

The fact that "&& has higher priority" relates to precedence (how tightly its operands stick to it) not "whose operands get evaluated first".

Because && is indeed above || in the table, the expression will be interpreted like this:

m = ++i || (++j && ++k)


Short circuit evaluation. If, the left-side of the && is non-zero, only then will the right-hand side be evaluated. Likewise, only if the left-hand side of the || is zero, will the right-hand side be evaluated.


"Higher operator precedence" is not the same as "evaluated first". When you use the short-circuiting operators, they are evaluated left-to-right. The results of any arithmetic will be affected by operator precedence, but that doesn't change the left-t0-right ordering of short circuiting.

The complexity of your example is a good reason for not doing this sort of thing. Even if you figure out the rules and know exactly what it will do, the next programmer to come along and look at the code probably won't.


Basically, || means, "if you have received something which is true, return that, otherwise, return whatever happens afterwards." So, the only thing which is evaluated there is m = (++i != 0). That means "increment i, assign m to the value of i compared to 0, break."

To be more specific, this is what is happening:

i = 1;
i = i + 1;
if( i ) {
   m = 1;
}
else { // who cares, this will never happen.
   j = j + 1;
   if( j ) {
       k = k + 1;
       m = (k != 0); // 1
   }
   else {
       m = 0;
   }
}


In the C language, there are two different issues you need to be aware of: operator precedence and order of evaluation.

Operator precedence determines which operator that gets its operands evaluated first, and also which operands that belong to which operator. For example in the expression a + b * c, the operator * has higher operator precedence than +. Therefore the expression will be evaluated as

a + (b * c)

All operators in the C language have deterministic precedence and they are the same on any compiler.

Order of evaluation determines which operand that gets evaluated first. Note that a sub-expression is also an operand. Order of evaluation is applied after the operator precedence has been determined. If the above a+b*c example has left-to-right order of evaluation, then the operands themselves get evaluated in the order a, b, c. If the operands were for example function calls, then the functions a(), b() and c() would have been executed in that order. All operands in this example need to be evaluated since they are all used. If the compiler can determine that some operands need not be evaluated, it can optimize them away, regardless of whether those operands contain side-effects (such as function calls) or not.

The problem is that order of evaluation of operands is most often unspecified behaviour, meaning that the compiler is free to evaluate either left-to-right or right-to-left, and we cannot know or assume anything about it. This is true for most operands in C, save for a few exceptions where the order of evaluation is always deterministic. Those are the operands of the operators || && ?: ,, where the order of evaluation is guaranteed to be left-to-right. (When using formal C language semantics, one says that there is a sequence point between the evaluation of the left and the right operator.)


So for the specific example m = ++i || ++j && ++k.

  • The unary prefix ++ operators have the highest precedence, binding the operators i, j and k to them. This syntax is pretty intuitive.
  • The binary && operator has 2nd highest precedence, binding the operators ++j and ++k to it. So the expression is equivalent to m = ++i || (++j && ++k).
  • The binary || operator has 3rd highest precedence, binding the operators i++ and (j++ && ++k)= to it.
  • The assignment operator = has the lowest precedence, binding the operators m and ++i || (++j && ++k) to it.

Further, we can see that both the || and the && operators are among those operators where the order of evaluation is guaranteed to be left to right. In other words, if the left operand of the || operator is evaluated as true, the compiler does not need to evaluate the right operand. In the specific example, ++i is always positive, so the compiler can perform quite an optimization, effectively remaking the expression to m = ++i;

If the value of i wasn't known at compile time, the compiler would have been forced to evaluate the whole expression. Then the expression would have been evaluated in this order:

  • && has higher precedence than ||, so start evaluating the && operator.
  • && operator is guaranteed to have order of evaluation of operands left-to-right, so perform ++j first.
  • If the result of ++j is true (larger than zero), and only then, then evaluate the right operator: perform ++k.
  • Store the result of ++j && ++k in a temporary variable. I'll call it j_and_k here. If both ++j and ++k were positive, then j_and_k will contain value 1 (true), otherwise 0 (false).
  • || operator is guaranteed to have order of evaluation of operands left-to-right, so perform ++i first.
  • If ++i is false (zero), and only then, evaluate the right operator "j_and_k". If one or both of them are positive, the result of the || operator is 1 (true), otherwise it is 0 (false).
  • m gets assigned the value 1 or 0 depending on the result.


You're seeing logical operator short-circuiting here. If the first part of an || condition is true, then it never evaluates the rest of the expression (because if the first part is a pointer-not-null check you wouldn't want to dereference the pointer in the second part if it's null). Further, since it's in a boolean-result expression the result of ++i is converted back to bool value 1 before being assigned into m.

Avoid this kind of code like the plague, it will only give you debugging nightmares in the short and long term.


Shortcut operators will cause the unnecessary expression components not to be evaluated. Since && has a higher precedence, it would need to be evaluated last if you want to allow the || operator to be able to shortcut the whole expression when ++i evaluates to true. Since this is the case, ++i is the only variable evaluated after the "=".


Precedence and order of evaluation are two different things. Both || and && evaluate their operands left-to-right; precedence doesn't change that.

Given the expression a || b && c, a will be evaluated first. If the result is 0, then b && c will be evaluated.


The order of comparison operators (|| and &&) is more important. That's why you'd better placed your most important test first.


because || and && short circuit and therefore specify sequence points

Note: this was originally tagged C++ as well, and you get a slightly different answer there as overloaded operators || or && do not short circuit just inbuilt ones

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号