If the value of the variable x
is initially 0, the expression x += x += 1
will evaluate to 2 in C, and to 1 in Javascript.
The semantics for C seems obvious to me: x += x += 1
is interpreted as x += (x += 1)
which is, in turn, equivalent to
x += 1
x += x // where x is 1 at this point
What is the logic behind Javascript's interpretation? What specification enforces such behaviour? (It should be noted, by the way, that Java agrees with Javascript here).
Update:
It turns out the expression x += x += 1
has undefined behaviour according to the C standard (thanks ouah, John Bode, DarkDust, Drew Dormann), which seems to spoil the whole point of the question for some readers. The expression can be made standards-compliant by inserting an identity function into it as follows: x += id(x += 1)
. The same modification can be made to the Javascript code and the question still remains as stated. Presuming that the majority of the readers can understand the point behind "non-standards-compliant" formulation I'll keep it as it is more concise.
Update 2: It turns out that according to C99 the introduction of the identity function is probably not solving the ambiguity. In this case, dear reader, please regard the original question as pertaining to C++ rather than C99, where "+=" can be most probably now safely be regarded as an overloadable operator with a uniquely defined sequence of operations. That is, x += x += 1
is now equivalent to operator+=(x, operator+=(x, 1))
. Sorry for the long road to standards-compliance.
JavaScript and Java have pretty much strict left-to-right evaluation rules for this expression. C does not (even in the version you provided that has the identity function intervening).
The ECMAScript spec I have (3rd Edition, which I'll admit is quite old – the current version can be found here: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) says that compound assignment operators are evaluated like so:
11.13.2 Compound Assignment ( op= )
The production AssignmentExpression : LeftHandSideExpression @ =
AssignmentExpression, where@ represents one of the operators indicated
above, is evaluated as follows:
- Evaluate LeftHandSideExpression.
- Call GetValue(Result(1)).
- Evaluate AssignmentExpression.
- Call GetValue(Result(3)).
- Apply operator @ to Result(2) and Result(4).
- Call PutValue(Result(1), Result(5)).
- Return Result(5)
You note that Java has the same behavior as JavaScript – I think its spec is more readable, so I'll post some snippets here (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.7):
15.7 Evaluation Order
The Java programming language guarantees that the operands of
operators appear to be evaluated in a specific evaluation order,
namely, from left to right.
It is recommended that code not rely crucially on this specification.
Code is usually clearer when each expression contains at most one side
effect, as its outermost operation, and when code does not depend on
exactly which exception arises as a consequence of the left-to-right
evaluation of expressions.
15.7.1 Evaluate Left-Hand Operand First The left-hand operand of a binary operator appears to be fully evaluated before any part of the
right-hand operand is evaluated. For example, if the left-hand operand
contains an assignment to a variable and the right-hand operand
contains a reference to that same variable, then the value produced by
the reference will reflect the fact that the assignment occurred
first.
...
If the operator is a compound-assignment operator (§15.26.2), then
evaluation of the left-hand operand includes both remembering the
variable that the left-hand operand denotes and fetching and saving
that variable's value for use in the implied combining operation.
On the other hand, in the not-undefined-behavior example where you provide an intermediate identity function:
x += id(x += 1);
while it's not undefined behavior (since the function call provides a sequence point), it's still unspecified behavior whether the leftmost x
is evaluated before the function call or after. So, while it's not 'anything goes' undefined behavior, the C compiler is still permitted to evaluate both x
variables before calling the id()
function, in which case the final value stored to the variable will be 1
:
For example, if x == 0
to start, the evaluation could look like:
tmp = x; // tmp == 0
x = tmp + id( x = tmp + 1)
// x == 1 at this point
or it could evaluate it like so:
tmp = id( x = x + 1); // tmp == 1, x == 1
x = x + tmp;
// x == 2 at this point
Note that unspecified behavior is subtly different than undefined behavior, but it's still not desirable behavior.
x += x += 1;
is undefined behavior in C.
The expression statement violates sequence points rules.
(C99, 6.5p2) "Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression."
In C, x += x += 1
is undefined behavior.
You can not count on any result happening consistently because it is undefined to try to update the same object twice between sequence points.
At least in C, this is undefined behavior. The expression x += x+= 1;
has two sequence points: an implicit one right before the expression starts (that is: the previous sequence point), and then again at the ;
. Between these two sequence points x
is modified twice and this explicitly stated as undefined behavior by the C99 standard. The compiler is free to do anything it likes at this point, including making daemons fly out of your nose. If you're lucky, it simply does what you expect but there is simply no guarantee for that.
This is the same reason why x = x++ + x++;
is undefined in C. See also the C-FAQ for more examples and explanations of this or the StackOverflow C++ FAQ entry Undefined Behavior and Sequence Points (AFAIK the C++ rules for this are the same as for C).
Several issues are at play here.
First and most important is this part of the C language specification:
6.5 Expressions
...
2 Between the previous and next sequence point an object shall have its stored value
modified at most once by the evaluation of an expression.72) Furthermore, the prior value
shall be read only to determine the value to be stored.73)
...
72) A floating-point status flag is not an object and can be set more than once within an expression.
73) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i = i + 1;
a[i] = i;
Emphasis mine.
The expression x += 1
modifies x
(side effect). The expression x += x += 1
modifies x
twice without an intervening sequence point, and it's not reading the prior value only to determine the new value to be stored; hence, the behavior is undefined (meaning any result is equally correct). Now, why on Earth would that be an issue? After all, +=
is right-associative, and everything's evaluated left-to-right, right?
Wrong.
3 The grouping of operators and operands is indicated by the syntax.74) Except as specified
later (for the function-call ()
, &&
, ||
, ?:
, and comma operators), the order of evaluation
of subexpressions and the order in which side effects take place are both unspecified.
...
74) 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).
Emphasis mine.
In general, precedence and associativity do not affect order of evaluation or the order in which side effects are applied. Here's one possible evaluation sequence:
t0 = x + 1
t1 = x + t0
x = t1
x = t0
Oops. Not what we wanted.
Now, other languages such as Java and C# (and I'm assuming Javascript) do specify that operands are always evaluated left-to-right, so there's always a well-defined order of evaluation.
All JavaScript expressions are evaluated left to right.
The associativity of...
var x = 0;
x += x += 1
will be...
var x = 0;
x = (x + (x = (x + 1)))
So because of its left to right evaluation, the current value of x
will be evaluated before any other operation takes place.
The result could be viewed like this...
var x = 0;
x = (0 + (x = (0 + 1)))
...which will clearly equal 1
.
So...
var x = 0;
x = (x + (x = (x + 1)));
// x = (0 + (x = (0 + 1))); // 1
x = (x + (x = (x + 1)));
// x = (1 + (x = (1 + 1))); // 3
x = (x + (x = (x + 1)));
// x = (3 + (x = (3 + 1))); // 7