Почему (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 имеет неопределенное поведение согласно стандарту C (спасибо ouah, John Bode, DarkDust, Drew Dormann), что, похоже, портит весь смысл вопроса для некоторых читателей. Это выражение можно сделать соответствующим стандартам, вставив в него функцию тождества следующим образом: x += id(x += 1). Такую же модификацию можно внести в код Javascript, и вопрос все равно останется открытым. Предполагая, что большинство читателей смогут понять смысл формулировки "не соответствующий стандартам", я оставлю ее, так как она более лаконична.

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

27
задан Community 23 May 2017 в 11:47
поделиться

2 ответа

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

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

11.13.2 Составное присваивание (op =)

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

  1. Evaluate LeftHandSideExpression.
  2. Вызовите GetValue (Результат (1)).
  3. Оценить AssignmentExpression.
  4. Вызовите GetValue (Результат (3)).
  5. Применить оператор @ к Результату (2) и Результату (4).
  6. Вызвать PutValue (Результат (1), Результат (5)).
  7. Return Result (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), то оценка левого операнда включает в себя как запоминание переменной, которую обозначает левый операнд, так и выборку и сохранение значения этой переменной для использования в подразумеваемая операция объединения.

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

x += id(x += 1);

, пока это не неопределенное поведение (поскольку вызов функции обеспечивает точку последовательности ), все еще не определено поведение независимо от того, вычисляется ли самый левый x до вызова функции или после. Таким образом, хотя это не неопределенное поведение «ничего не происходит», компилятору 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

Обратите внимание, что не указано поведение немного отличается от неопределенного поведения, но все же это нежелательное поведение.

15
ответ дан 28 November 2019 в 04:39
поделиться

Несколько вопросов находятся здесь.

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

6.5 Выражения
...
2 Между предыдущей и следующей точкой последовательности объект должен иметь свое сохраненное значение, измененное не более одного раза путем оценки выражения. 72) Кроме того, предыдущее значение должно быть только для чтения чтобы определить значение для сохранения. 73)
...
72) Флаг состояния с плавающей точкой не является объектом и может быть установлен более одного раза в выражении.

73) В этом абзаце отображаются неопределенные операторные выражения, такие как
    i = ++i + 1;
    a[i++] = i;
, в то же время допускается
    i = i + 1;
    a[i] = i;

Выделение шахты.

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

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

3 Группировка операторов и операндов указывается синтаксисом. 74) За исключением случаев, указанных позже (для вызова функций (), &&, ||, ?: и операторы запятой), порядок вычисления подвыражений и порядок возникновения побочных эффектов не определены .
...
74) Синтаксис определяет приоритет операторов при вычислении выражения, который совпадает с порядком основных подпунктов этого подпункта, причем сначала самый высокий приоритет. Так, например, выражения, разрешенные в качестве операндов бинарного оператора + (6.5.6), являются выражениями, определенными в 6.5.1–6.6.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 и C # (и я предполагаю, что Javascript) , делают , указывают, что операнды всегда вычисляются слева направо, так что всегда есть четко определенный порядок оценки. [+1139]

5
ответ дан 28 November 2019 в 04:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: