From the online draft of the 2011 language standard:
6.5 Expressions
...
3 The grouping of operators and operands is indicated by the syntax.85) Except as specified later, side effects and value computations of subexpressions are unsequenced.86)
85) The syntax specifies the precedence of operators in the evaluation of an expression, which is the same as the order of the major subclauses of this subclause, highest precedence first. Thus, for example, the expressions allowed as the operands of the binary+
operator (6.5.6) are those expressions defined in 6.5.1 through 6.5.6. The exceptions are cast expressions (6.5.4) as operands of unary operators (6.5.3), and an operand contained between any of the following pairs of operators: grouping parentheses()
(6.5.1), subscripting brackets[]
(6.5.2.1), function-call parentheses()
(6.5.2.2), and the conditional operator? :
(6.5.15). Within each major subclause, the operators have the same precedence. Left- or right-associativity is indicated in each subclause by the syntax for the expressions discussed therein.
86) In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations.
Clear as mud, right? What it means is that, given an expression like
x = a++ + b++ * (--c / ++d)
each of the subexpressions a++
, b++
, --c
, and ++d
may be evaluated in any order; just because --c
and ++d
are grouped by parens doesn't mean they're evaluated first. Furthermore, the side effects of each ++
and --
don't have to be applied immediately after the expression is evaluated.
All operator precedence guarantees is that the result of --c / ++d
will be multiplied by the result of b++
, and the result of a++
will be added to that value; it does not guarantee that any expression is evaluated before any other.
Pay close attention to footnote 86; if the above expression appeared in a loop, there's no reason to expect that the subexpressions would be evaluated in the same order every time through the loop. As a practical matter, they most likely will be, but the compiler is explicitly given the freedom to shake things up.
Because of this freedom to evaluate expressions and apply side effects in any order, certain expressions like a++ + a++
won't give consistent results; the standard explicitly calls this out as undefined behavior, meaning the compiler isn't obligated to handle the situation in any particular way. It can ignore the issue, it can issue a warning, it can halt translation with an error, etc., but there's no requirement that it do any one particular thing.