-1

C programming language documentation Precedence and order of evaluation states:

The direction of evaluation does not affect the results of expressions that include more than one multiplication (*), addition (+), or binary-bitwise (&, |, or ^) operator at the same level. Order of operations is not defined by the language.

What exactly does the above mean (perhaps a code example will help)?

Chris
  • 26,361
  • 5
  • 21
  • 42
  • [C operator precedence](https://en.cppreference.com/w/c/language/operator_precedence) essentially defines what takes precedence -- where there is no definition to discriminate the order of operation (like two `*` operations), the order is left to the implementation. Though `*` does have `Left to Right` association. – David C. Rankin Dec 07 '21 at 02:27
  • 1
    It is saying that `a * b * c` is equal to `b * c * a` -- so the direction of evaluation does not matter. Therefore the compiler is free to implement the order however it sees fit -- so long as it provides the right answer. – David C. Rankin Dec 07 '21 at 02:31
  • The compiler is free to multiply the values `a`, `b`, and `c` in any order it likes; it is also free to evaluate that term before or after evaluating `d * e`. Clearly, it must fully evaluate both sets of multiplications before it can do the addition. So, for the most part, `a * b * c + d * e` is not really what it is talking about. But if you had `a * b * c + d * e * f + g * h * i`, then there are multiple places where the excerpt applies (each set of 2 multiplications, and the set of 2 additions). – Jonathan Leffler Dec 07 '21 at 02:43
  • That so-called documentation is wrong. Order of evaluation makes a difference for floating-point operations and for side effects. It can also affect whether or not overflow occurs in integer operations. – Eric Postpischil Dec 07 '21 at 02:49
  • See [What is the difference between operator precedence and order of evaluation?](https://software.codidact.com/posts/278172) – Lundin Dec 07 '21 at 14:00
  • I voted to close this because it is not really a question about C precedence, grammar, evaluation, and such. Rather, it is a question about the intent of the author of poorly written text. As such, it is not really a programming question at all, so it does not belong on Stack Overflow. This text is not a useful aid for learning, and people ought to simply disregard it and find better sources of information. – Eric Postpischil Dec 07 '21 at 14:35

2 Answers2

3

That page is not particularly well-written.

Precedence determines which operators are grouped with which operands in an expression - it does not dictate the order in which subexpressions are evaluated. For example, in the expression a + b * c, the * operator has higher precedence than the + operator, so the expression is parsed as a + (b * c) - the result of a is added to the result of b * c.

However, each of the expressions a, b, and c may be evaluated in any order, even simultaneously (interleaved or in parallel). There’s no requirement that b be evaluated before c or that either must be evaluated before a.

Associativity also affects grouping of operators and operands when you have multiple operators of the same precedence - the expression a + b + c is parsed as (a + b) + c because the + operator (along with the other arithmetic operators) is left-associative. The result of a + b is added to the result of c.

But like with precedence above, this does not control order of evaluation. Again, each of a, b, and c may be evaluated in any order.

The only operators which force left-to-right evaluation of their operands are the &&, ||, ?:, and comma operators.

From the comments:

Because the operands b and c accompany the * (multiplication) operator which has higher precedence than the + (addition operator), then isn't it required (always) that both b and c be evaluated first before a?

The only requirement is that the result of b * c be known before it can be added to the result of a. It doesn’t mean that b * c must be evaluated before a:

t0 <- a
t1 <- b * c
t2 <- t1 + t0

Again, precedence only controls the grouping of operators and operands, not the order in which subexpressions are evaluated.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • UV "... in any order, _even simultaneously_" and the listing of exceptions. – chux - Reinstate Monica Dec 07 '21 at 04:39
  • 1
    @Turtlefight: C does not have overloads. – Eric Postpischil Dec 07 '21 at 05:21
  • @GreenTea78 In `a + b * c` there are perhaps five questions: (1) When does `a` get evaluated? (2) When does `b` get evaluated? (3) When does `c` get evaluated? (4) When does the `+` get evaluated? (5) When does the `*` get evaluated? And the point is that there's almost nothing we can say about (1), (2), and (3). All we can say is that (a) 1, 2, and 3 happen before 4 and 5, and (b) 5 happens before 4. Everything else is unspecified — unless 1, 2, or 3 involve side effects on the same variable, in which case everything becomes undefined. – Steve Summit Dec 07 '21 at 12:15
  • See also [this answer](https://stackoverflow.com/questions/31087537/why-does-a-b-have-the-same-behavior-as-a-b/31088592#31088592). – Steve Summit Dec 07 '21 at 12:17
  • @GreenTea78: see my edit. – John Bode Dec 07 '21 at 13:00
2

I assume that what the cited documentation is trying to say is that given the code

a = f1() + f2() + f3();

or

b = f1() * f2() * f3();

we do not know which of the functions f1, f2, or f3 will be called first.

However, it is guaranteed that the result of calling f1 will be added to the result of calling f2, and that this intermediate sum will then be added to the result of calling f3. Similarly for the multiplications involved in computing b. These aspects of the evaluation order are guaranteed due to the left-associativity of addition and multiplication. That is, the results (both the defined and the unspecified aspects) are the same as if the expressions had been written

a = (f1() + f2()) + f3();

and

b = (f1() * f2()) * f3();

Upon reading the cited documentation, however, I fear that I may be wrong. It's possible that the cited documentation is simply wrong, in that it seems to be suggesting that the +, *, &, |, and ^ are somehow an exception to the associativity rules, and that the defined left-associativity is somehow not applicable. That's nonsense, of course: left-associativity is just as real when applied to these operators as it is when applied to, say, - and /.

To explain: If we write

10 - 5 - 2

it is unquestionably equivalent to

(10 - 5) - 2

and therefore results in 3. It is not equivalent to

10 - (5 - 2)

and the result is therefore not 7. Subtraction is not commutative and not associative, so the order you do things in almost always matters.

In real mathematics, of course, addition and multiplication are fully commutative and associative, meaning that you can mix things up almost any which way and still get the same result. But what's not as well known is that computer mathematics are significantly enough different from "real" mathematics that not all of the rules — in particular, commutativity — actually apply.

Consider the operation

-100000000 + 2000000000 + 200000000

If it's evaluated the way I've said it has to be, it's

(-100000000 + 2000000000) + 200000000

which is

1900000000 + 200000000

which is 2100000000, which is fine.

If someone (or some compiler) chose to evaluate it the way I've said it couldn't be evaluated, on the other hand, it might come out as

-100000000 + (2000000000 + 200000000)    /* WRONG */

which is

-100000000 + 2200000000

which is... wait a minute. We're in trouble already. 2200000000 is a 32-bit number, which means it can't be properly represented as a positive, 32-bit signed integer.

In other words, this is an example of an expression which, if you evaluate it in the wrong order, can overflow, and theoretically become undefined.

Similar things can happen with floating-point arithmetic. The expression

1.2e-50 * 3.4e300 * 5.6e20

will overflow (exceed the maximum value of a double, which is good up to about 1e307) if the second multiplication wrongly happens first. The expression

2.3e100 * 4.5e-200 * 6.7e-200

will underflow (to zero, exceeding the minimum value of a double) if the second multiplication happens first.

The point I'm trying to make here is that computer addition and multiplication are not quite commutative, meaning that a compiler should not rearrange them. If a compiler does (as the cited documentation seems to, wrongly, claim is possible), you, the programmer, can see results which are significantly and wrongly different from what the C Standard said you were allowed to expect.

I hope this all makes some kind of sense, although in closing, I should perhaps suggest that it's not necessarily as unambiguous and clear-cut as I've made it sound. I believe what I've described (that is, the strict associativity, and non commutativity, of multiplication and addition) is what's formally required by the current C standards, and by IEEE-754. However, I'm not sure they've been required by all versions of the C Standard, and I don't believe they were clearly guaranteed by Ritchie's original definition of C, either. They're not guaranteed by all C compilers, they're not depended upon or cared about by all C programmers, and they're not appreciated by people who write documentation like that cited in this thread.

(Also, for those really interested in fine points, rearranging integer addition as if it were commutative is not quite so wrong — or, at least, it's not visible/detectably wrong — if you know you're compiling for a processor that quietly wraps around on signed integer overflow.)

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • I think the documentation is trying to make the point that `f1`, `f2`, and `f3` can be called in any order (whether or not there are parentheses), but it is badly worded – M.M Dec 07 '21 at 03:28