简体   繁体   English

为什么(x + = x + = 1)在C和Javascript中的评价方式不同?

[英]Why does (x += x += 1) evaluate differently in C and Javascript?

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. 如果变量x值最初为0,则表达式x += x += 1将在C中评估为2,在Javascript中评估为1。

The semantics for C seems obvious to me: x += x += 1 is interpreted as x += (x += 1) which is, in turn, equivalent to C的语义对我来说似乎很明显: x += x += 1被解释为x += (x += 1) ,这反过来相当于

x += 1
x += x  // where x is 1 at this point

What is the logic behind Javascript's interpretation? Javascript解释背后的逻辑是什么? What specification enforces such behaviour? 什么规范强制执行这种行为? (It should be noted, by the way, that Java agrees with Javascript here). (顺便说一句,应该注意Java在这里与Javascript一致)。

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. 更新:事实证明表达式x += x += 1具有根据C标准的未定义行为(感谢ouahJohn BodeDarkDustDrew Dormann ),这似乎破坏了一些读者的问题的全部要点。 The expression can be made standards-compliant by inserting an identity function into it as follows: x += id(x += 1) . 通过在其中插入标识函数,可以使表达式符合标准: x += id(x += 1) The same modification can be made to the Javascript code and the question still remains as stated. 可以对Javascript代码进行相同的修改,问题仍然如所述。 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. 更新2:事实证明,根据C99,身份函数的引入可能无法解决模糊性。 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. 在这种情况下,亲爱的读者,请将原始问题视为与C ++而不是C99有关,其中“+ =”现在可能最安全地被视为具有唯一定义的操作序列的可重载运算符。 That is, x += x += 1 is now equivalent to operator+=(x, operator+=(x, 1)) . 也就是说, x += x += 1现在等于operator+=(x, operator+=(x, 1)) Sorry for the long road to standards-compliance. 对于通向标准的漫长道路感到抱歉。

x += x += 1; is undefined behavior in C. 在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." (C99,6.5p2)“在前一个和下一个序列点之间,一个对象的存储值最多只能通过表达式的评估来修改一次。”

JavaScript and Java have pretty much strict left-to-right evaluation rules for this expression. JavaScript和Java对此表达式具有非常严格的从左到右的评估规则。 C does not (even in the version you provided that has the identity function intervening). C不会(即使在您提供的具有身份功能干预的版本中)。

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: 我有ECMAScript规范(第3版,我承认它已经很老了 - 目前的版本可以在这里找到: http//www.ecma-international.org/publications/files/ECMA-ST/Ecma-262。 pdf )表示复合赋值运算符的计算如下:

11.13.2 Compound Assignment ( op= ) 11.13.2复合赋值(op =)

The production AssignmentExpression : LeftHandSideExpression @ = AssignmentExpression, where@ represents one of the operators indicated above, is evaluated as follows: 生产AssignmentExpression:LeftHandSideExpression @ = AssignmentExpression,其中@代表上面指出的一个运算符,评估如下:

  1. Evaluate LeftHandSideExpression. 评估LeftHandSideExpression。
  2. Call GetValue(Result(1)). 调用GetValue(Result(1))。
  3. Evaluate AssignmentExpression. 评估AssignmentExpression。
  4. Call GetValue(Result(3)). 调用GetValue(Result(3))。
  5. Apply operator @ to Result(2) and Result(4). 将operator @应用于Result(2)和Result(4)。
  6. Call PutValue(Result(1), Result(5)). 调用PutValue(结果(1),结果(5))。
  7. Return Result(5) 返回结果(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 ): 您注意到Java具有与JavaScript相同的行为 - 我认为其规范更具可读性,因此我将在此处发布一些片段( http://java.sun.com/docs/books/jls/third_edition/html/expressions。 html#15.7 ):

15.7 Evaluation Order 15.7评估订单

The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right. Java编程语言保证运算符的操作数似乎以特定的评估顺序进行评估,即从左到右。

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. 15.7.1首先评估左侧操作数在评估右侧操作数的任何部分之前,似乎完全评估了二元运算符的左侧操作数。 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. 如果运算符是复合赋值运算符(第15.26.2节),那么对左侧操作数的计算包括记住左侧操作数表示的变量并获取并保存该变量的值以用于隐含的组合操作。

On the other hand, in the not-undefined-behavior example where you provide an intermediate identity function: 另一方面,在提供中间身份函数的not-undefined-behavior示例中:

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. 虽然它不是未定义的行为(因为函数调用提供了一个序列点),但是在函数调用之前或之后是否评估最左边的x仍然是未指定的行为。 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 : 因此,虽然它不是'任何事情'未定义的行为,但仍然允许C编译器在调用id()函数之前评估两个x变量,在这种情况下,存储到变量的最终值将为1

For example, if x == 0 to start, the evaluation could look like: 例如,如果x == 0开始,评估可能如下所示:

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. 请注意,未指定的行为与未定义的行为略有不同,但它仍然不是理想的行为。

In C, x += x += 1 is undefined behavior . 在C中, x += x += 1未定义的行为

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. 至少在C中,这是未定义的行为。 The expression x += x+= 1; 表达式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. 在这两个序列点之间x被修改两次,这明确地表示为C99标准的未定义行为。 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++; 这与x = x++ + x++;原因相同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). 另请参阅C-FAQ以获取更多示例和对此的解释或StackOverflow C ++ FAQ条目未定义的行为和序列点 (AFAIK的C ++规则与C相同)。

Several issues are at play here. 这里有几个问题。

First and most important is this part of the C language specification: 首先也是最重要的是C语言规范的这一部分:

6.5 Expressions 6.5表达式
... ...
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. 2在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次 72) Furthermore, the prior value shall be read only to determine the value to be stored. 72)此外,先前值应只读以确定要存储的值。 73) 73)
... ...
72) A floating-point status flag is not an object and can be set more than once within an expression. 72)浮点状态标志不是对象,可以在表达式中多次设置。

73) This paragraph renders undefined statement expressions such as 73)此段落呈现未定义的语句表达式,如
\n    i = ++i + 1; i = ++ i + 1;\n    a[i++] = i; a [i ++] = i;\n
while allowing 允许的同时
\n    i = i + 1; i = i + 1;\n    a[i] = i; a [i] = i;\n

Emphasis mine. 强调我的。

The expression x += 1 modifies x (side effect). 表达式x += 1修改x (副作用)。 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; 表达式x += x += 1修改x两次而没有插入序列点,并且它不是只读取先前值来确定要存储的新值; 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. 3语法指示运算符和操作数的分组。 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)除了后面指定的(对于函数调用()&&||?:和逗号运算符), 子表达式的评估顺序和副作用发生的顺序都是未指定的
... ...
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. 74)语法指定运算符在表达式求值中的优先级,该子表达式与本子条款的主要子条款的顺序相同,优先级最高。 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. 因此,例如,允许作为二元+运算符(6.5.6)的操作数的表达式是6.5.1到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). 例外是强制转换表达式(6.5.4)作为一元运算符的操作数(6.5.3),以及下列任何运算符对之间包含的操作数:分组括号() (6.5.1),下标括号[] (6.5 .2.1),函数调用括号() (6.5.2.2)和条件运算符?: 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. 现在,其他语言,如Java和C#(我假设Javascript) 确实指定操作数总是从左到右进行评估,因此总是有一个明确定义的评估顺序。

All JavaScript expressions are evaluated left to right. 所有JavaScript表达式都是从左到右计算的。

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. 因此,由于其从左到右的评估, x的当前值将在任何其他操作发生之前进行评估。

The result could be viewed like this... 结果可以这样看......

var x = 0;
x = (0 + (x = (0 + 1)))

...which will clearly equal 1 . ......显然等于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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM