Подтвердить что ты не робот

Почему (x + = x + = 1) оценивается по-разному в C и Javascript?

Если значение переменной x изначально равно 0, выражение x += x += 1 будет оцениваться как 2 в C и 1 в Javascript.

Семантика для C кажется мне очевидной: x += x += 1 интерпретируется как x += (x += 1), который, в свою очередь, эквивалентен

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

В чем заключается логика интерпретации Javascript? Какая спецификация обеспечивает такое поведение? (Следует отметить, кстати, что Java соглашается с Javascript здесь).

Update: Оказывается, выражение x += x += 1 имеет поведение undefined в соответствии со стандартом C (спасибо ouah, Джон Боде, DarkDust, Дрю Дорманн), который, кажется, испортил всю точку вопрос для некоторых читателей. Выражение можно сделать стандартным, вставив в него функцию идентификации: x += id(x += 1). Та же модификация может быть внесена в код Javascript, и вопрос остается прежним. Предполагая, что большинство читателей могут понять суть "нестандартной" формулировки, я сохраню ее, поскольку она более кратка.

Обновление 2: Оказывается, что, согласно C99, введение функции тождества, вероятно, не решает двусмысленность. В этом случае уважаемый читатель, пожалуйста, рассмотрите исходный вопрос как относящийся к С++, а не к C99, где "+ =" теперь, вероятно, теперь можно смело считать перегружаемым оператором с однозначно определенной последовательностью операций. То есть x += x += 1 теперь эквивалентно operator+=(x, operator+=(x, 1)). Извините за долгий путь к соблюдению стандартов.

4b9b3361

Ответ 1

JavaScript и Java имеют довольно строгие правила оценки слева направо для этого выражения. C не (даже в версии, которую вы предоставили, которая имеет функцию идентификации).

Спецификация ECMAScript у меня есть (3-е издание, которое я признаю довольно старым: текущую версию можно найти здесь: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) говорит, что сложные операторы присваивания вычисляются так:

11.13.2 Назначение соединения (op =)

Производство AssignmentExpression: LeftHandSideExpression @= Выделение присваивания, где @представляет один из указанных операторов выше, оценивается следующим образом:

  • Вычислить значение LeftHandSideExpression.
  • Вызов GetValue (Результат (1)).
  • Вычислить присвоение.
  • Вызов GetValue (Результат (3)).
  • Применить оператор @к результату (2) и результату (4).
  • Вызов PutValue (Результат (1), Результат (5)).
  • Результат возврата (5)

Вы заметили, что Java имеет такое же поведение, что и JavaScript - я думаю, что его спецификация более читаема, поэтому я опубликую некоторые фрагменты здесь (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.7):

15.7 Порядок оценки

Язык программирования Java гарантирует, что операнды операторы, по-видимому, оцениваются в определенном порядке оценки, а именно слева направо.

Рекомендуется, чтобы код не опирался на эту спецификацию. Код обычно яснее, когда каждое выражение содержит не более одной стороны эффект, как его внешняя операция, и когда код не зависит от именно это исключение возникает как следствие слева направо оценка выражений.

15.7.1 Оценка левого операнда Сначала левый операнд двоичного оператора оказывается полностью оцененным до того, как любая часть правый операнд. Например, если левый операнд содержит назначение переменной и правый операнд содержит ссылку на эту же переменную, тогда значение, полученное ссылка будет отражать тот факт, что произошло присвоение первый.

...

Если оператор является оператором составного присваивания (§ 15.26.2), то оценка левого операнда включает в себя как запоминание переменная, которую левый операнд обозначает и извлекает и сохраняет это переменное значение для использования в предполагаемой операции объединения.

С другой стороны, в примере non- undefined -behavior, где вы предоставляете промежуточную функцию идентификации:

x += id(x += 1);

в то время как это не поведение undefined (так как вызов функции предоставляет точку последовательности), это все еще неопределенное поведение, если левый x оценивается перед вызовом функции или после. Поэтому, хотя поведение "ничего не идет" undefined, компилятору C по-прежнему разрешено оценивать переменные x перед вызовом функции id(), и в этом случае конечное значение, сохраненное в переменной, будет 1

Например, если x == 0 для начала, оценка может выглядеть так:

tmp = x;    // tmp == 0
x = tmp  +  id( x = tmp + 1)
// x == 1 at this point

или он может оценить его так:

tmp = id( x = x + 1);   // tmp == 1, x == 1
x = x + tmp;
// x == 2 at this point

Обратите внимание, что неопределенное поведение тонко отличается от поведения undefined, но оно все еще нежелательно.

Ответ 2

x += x += 1; - это поведение undefined в C.

Оператор выражения нарушает правила точек очередности.

(C99, 6.5p2) "Между предыдущей и следующей точкой последовательности объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения."

Ответ 3

В C, x += x += 1 работает undefined.

Вы не можете рассчитывать на какой-либо результат, который последовательно повторяется, потому что он undefined пытается дважды обновить один и тот же объект между точками последовательности.

Ответ 4

По крайней мере, в C это поведение undefined. Выражение x += x+= 1; имеет две точки последовательности: неявное одно прямо перед началом выражения (то есть: предыдущая точка последовательности), а затем снова на ;. Между этими двумя точками последовательности x изменяется дважды, и это явно указано как поведение undefined по стандарту C99. Компилятор может свободно делать все, что ему нравится, в том числе, чтобы заставить демонов вылететь из вашего носа. Если вам повезет, это просто делает то, что вы ожидаете, но для этого нет никакой гарантии.

Это та же самая причина, почему x = x++ + x++; есть undefined в C. См. также C-FAQ для получения дополнительных примеров и объяснений этой или записи в FAQ StackOverflow С++ Undefined Поведение и точки последовательности (AFAIK правила С++ для этого те же, что и для C).

Ответ 5

Здесь есть несколько вопросов.

Прежде всего, это самая важная часть спецификации языка C:

6.5 Выражения
...
2 Между предыдущей и следующей точкой последовательности объект должен иметь сохраненное значение модифицированный не чаще путем оценки выражения. 72) Кроме того, предыдущее значение должны быть прочитаны только для определения сохраненного значения. 73)
...
72) Флаг состояния с плавающей запятой не является объектом и может быть установлен более одного раза в выражении. 73) Этот параграф отображает выражения выражения undefined, такие как
    i = ++i + 1;
    a[i++] = i;
позволяя
    i = i + 1;
    a[i] = i;

Акцент на мой.

Выражение x += 1 изменяет x (побочный эффект). Выражение x += x += 1 дважды меняет x без промежуточной точки последовательности и не считывает предыдущее значение только для определения нового значения, которое нужно сохранить; следовательно, поведение undefined (что означает, что любой результат одинаково корректен). Теперь, почему на Земле это будет проблемой? В конце концов, += является право-ассоциативным, и все оценивается слева направо, правильно?

Неправильно.

3 Группировка операторов и операндов обозначается синтаксисом. 74) Кроме указанных позже (для функции-вызова (), &&, ||, ?: и запятых), порядок оценки подвыражений и порядок, в котором происходят побочные эффекты, являются неуказанными.
...
74) Синтаксис определяет приоритет операторов при оценке выражения, которое является одним и тем же как порядок основных подпунктов этого подпункта, наивысший приоритет в первую очередь. Так, например, выражения, разрешенные как операнды двоичного оператора + (6.5.6), являются выражениями, определенными в 6.5.1 - 6.5.6. Исключениями являются выраженные выражения (6.5.4) в качестве операндов унарных операторов (6.5.3) и операнд, содержащийся между любой из следующих пар операторов: группировка круглые скобки () (6.5.1), скобки для подписи [] (6.5.2.1), круглые скобки функций () (6.5.2.2) и условный оператор ?: (6.5.15).

Акцент на мой.

В целом, приоритет и ассоциативность не влияют на порядок оценки или порядок, в котором применяются побочные эффекты. Здесь возможна одна возможная последовательность оценки:

 t0 = x + 1 
 t1 = x + t0
 x = t1
 x = t0

К сожалению. Не то, что мы хотели.

Теперь другие языки, такие как Java и С# (и я предполагаю Javascript), задают, что операнды всегда оцениваются слева направо, поэтому всегда существует четко определенный порядок оценки.

Ответ 6

Все выражения JavaScript оцениваются слева направо.

Ассоциативность...

var x = 0;
x += x += 1

будет...

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

Поэтому из-за оценки слева направо текущее значение x будет оцениваться до начала любой другой операции.

Результат можно просмотреть следующим образом:

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

..., который будет явно равен 1.

Итак...

   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