1

Consider the following code:

int y, x; y = 2; x = 0;

x!=0&&y/x>5

My lecturer's textbook on C provides a table for "Precedence hierarchy for the arithmetic, relational, and logical operators" using this table to evaluate the operators in the above expression I get the following precedence:

(1) is /, therefore, do y/x first

(2) is >, (3) is !=, (4) is &&.

If I evaluate the above code using this precedence hierarchy, then the first subexpr to be evaluated is: y / x which is 2 / 0, or undefined...

The given answer is 0.

Later on, the textbook also states that, per the ANSI C standard, the operand on the left of && will always be evaluated first, and only if this is 0 will the operand on the right be evaluated.

Does this mean that whenever a logical and (&&) (or a logical or (||) for that matter) appears in an expression along with arithmetic and relational operators, that effectively I need to separate the terms of the overall expression into two groups - terms on the left, and terms on the right, of the &&, and ONLY THEN begin applying the "Precedence hierarchy for the arithmetic, relational, and logical operators" to the operators contained in the overall 'left hand operand'?

This would result in the above expression being evaluated as:

(x!=0) && (y/x>5)

starting with the left operand, (x!=0), which is (0!=0), which is false, so 0, and thus no further evaluation takes place.

--

If this is the case, why does the && operator appear so low down the hierarchy of precedence if its inclusion in an expression dictates what must be done first?

Werner Henze
  • 16,404
  • 12
  • 44
  • 69
Andrew Tol
  • 11
  • 1
  • Precedence controls the grouping of operators and operands - it does not control the order in which expressions are evaluated. – John Bode Feb 06 '21 at 23:28

3 Answers3

1

In this case, you're encountering something called "short circuit evaluation". Think of your condition if (x != 0 && y / x > 5) as if (somethingIsTrue && somethingIsTrue). In order for that to be true, it must become if (true && true). In short circuit evaluation, it sees that x !=0 is false and immediately stops evaluating there because no matter what comes after it, the first thing is false so it can never be true.

And yes, to your point, you can sort of think of it as splitting the expressions into groups between the && and || statements. Each of those individual expressions gets evaluated as true or false, and then just look at it as a bunch of true or false statements in between && and || statements. So, for something like if (blah1 && blah2 || blah3), no matter what blah1, blah2, and blah3 are, they will all get evaluated to true or false. And then you just see how it plays out like if (true && true || false) or something like that.

And on a side note, don't bang your head against a wall trying to memorize precedence rules. Most of them are intuitive and you'll get the hang of the correct way of doing things as you program more.

Ben
  • 195
  • 7
0

The precedence of operator makes clear how your expression x!=0&&y/x>5 is to be interpreted. Think about it as setting braces. So the compiler would read your expression as (x!=0) && ((y/x)>5). Note here that the operators with the highest precedence are getting braces first, the ones with lower precedence are getting braces later.

Now the evaluation takes place. Evaluation is done from outer to inner. The outmost operator is the one with the lowest priority, the &&. It has two operands, the left one and the right one. Now the standard says that the left one is to be evaluated first. If it is false, then the && returns false (shortcut). If it is true, then the right operand needs to be evaluated.

The good thing about this guaranteed evaluation order is that you can now safely write for example if (x!=0 && y/x>10) or if (pointer!=NULL && pointer->data != 3).

Werner Henze
  • 16,404
  • 12
  • 44
  • 69
0

Precedence and associativity only say how to structure an expression. They do not say how to evaluate it. Consider x+y*z. Were it not for precedence, we could structure this expression as (x+y)*z, shown on the left, or x+(y*z), shown on the right.

    *                +
   / \              / \
  +   z            x   *
 / \                  / \
x   y                y   z

Precedence tells us to use the latter. But we can still evaluate x, y, and z in any order. Suppose x, y, and z are actually function calls with side effects; perhaps they print “x”, “y”, and “z”. If we evaluate z and remember its result, then evaluate y, then multiply them, then evaluate x, and then add, we will get the same result as if we evaluate x, then y, then z, then multiply, then add.

Both of these orders of evaluation use the same expression structure, the latter one. So the precedence that gave us the expression structure did not tell us how to evaluate the expression. (Almost—the structure does compel us to get the result of the multiplication before we can use it in the addition.)

The && and || operators have a property beyond precedence: They have a rule that their left operand is evaluated first, and the right operand is evaluated only if the left operand does not determine the result.

Digging deeper, there are two computations associated with some expressions. Each expression has its main effect: to produce a value, such as the sum, product, or logical AND of its operands. Some expressions also have side effects, such as incrementing an object (in x++) or writing output (putchar('c')). The side effect does not have to be done at the same time as the main effect. Generally, it can be done any time during the full expression it is in, and it can be before, during, or after the main effect.

The ordering property of the && and || operators extends to side effects: They require all side effects of their left operand be completed before any part of the right operand is evaluated.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312