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

Секвенирование оператора присваивания в выражениях C11

Введение

В стандарте C11 (ISO/IEC 9899: 2011) введено новое определение последовательности побочных эффектов в выражении (см. соответствующий вопрос). Концепция точки последовательности была дополнена секвенированными ранее и упорядочена после отношений, которые теперь являются основой для всех определений.

Раздел 6.5 "Выражения", пункт 2 гласит:

Если побочный эффект скалярного объекта не зависит от другого побочного эффекта на том же скалярном объекте или вычислении значения, используя значение одного и того же скаляра объект, поведение undefined. Если имеется несколько допустимых порядков подвыражения выражения, поведение undefined, если такая непоследовательная сторона эффект происходит в любом из упорядочений.

В дальнейшем в разделе 6.5.16 "Операторы присваивания" точка 3 указывает:

Побочный эффект обновления сохраненного значения левого операнда секвенируется после вычисления значений левого и правого операндов. Оценки операнды не подвержены влиянию.

Проблема

Первый цитируемый параграф (6.5/2) поддерживается двумя примерами (такими же, как в стандарте C99):

Первый пример

a[i++] = i;  //! undefined
a[i] = i;    //  allowed

Это можно легко объяснить с помощью определений:

  • Если побочный эффект скалярного объекта не влияет на (...) вычисление значения с использованием значения одного и того же скалярного объекта, поведение undefined. (6,5/2),
  • Оценки операндов не имеют никакого значения. [в пределах задания] (6.5.16/3).

Таким образом, побочный эффект i++ (LHS) не зависит от i (RHS), что дает поведение undefined.

Второй пример

i = ++i + 1; //! undefined
i = i + 1;   //  allowed

Этот код, однако, приводит к определенному поведению в обоих случаях:

  • побочный эффект обновления сохраненного значения левого операнда упорядочивается после вычисления значений левого и правого операндов.

Итак, выполнение ++i + 1 должен предшествовать побочному эффекту обновления i, что означает, что побочный эффект не сказывается на скалярном объекте, не подверженном влиянию либо на другой побочный эффект на один и тот же скалярный объект, либо на вычисление значения с использованием значения одного и того же скалярного объекта.

Вопрос

Эти примеры легко объяснить с помощью терминов и определений, представленных стандартом C99 (см. соответствующий вопрос). Но почему i = ++i + 1 undefined в соответствии с терминологией C11?

4b9b3361

Ответ 1

Обновить

Я меняю свой ответ здесь, это не так четко определено в C11, хотя оно находится в С++ 11. Ключ здесь состоит в том, что результат ++i не является значением lvalue и поэтому не требует преобразования lvalue-to-rvalue после того, как ++i оценивается, и поэтому мы не можем быть уверены, что результат ++i будет считаться после этого. Это отличается от С++, поэтому отчет о дефекте, который я изначально связал с привязкой к этому критическому факту:

[...] выражение lvalue ++ i, а затем преобразование lvalue-to-rvalue в результат. гарантирует, что побочный эффект приращения секвенирован перед вычислением операции сложения [...]

мы можем это увидеть, перейдя в C11 draft standard раздел 6.5.3.1 Операторы приращения и уменьшения префиксов, которые гласят:

[...] Выражение ++ E эквивалентно (E + = 1). [...]

а затем раздел 6.5.16 Операторы присваивания, которые говорят (акцент мой вперед):

Оператор присваивания сохраняет значение в объекте, обозначенном левым операндом. выражение присваивания имеет значение левого операнда после присваивания, 111, но не lvalue. [...]

и примечание 111 говорит:

Реализация разрешена для чтения объекта для определения значения, но не требуется, даже если у объекта есть нестабильный тип.

Нет необходимости читать объект, чтобы определить его значение, даже если оно нестабильно.

Оригинальный ответ

Насколько я могу судить, это на самом деле хорошо определено, и этот пример был удален из стандартного проекта С++, который использует похожий язык. Мы можем видеть это в 637. Правила последовательности и пример не согласен, в котором говорится:

следующее выражение все еще указано в качестве примера поведения undefined:

i = ++i + 1;

Однако, похоже, что новые правила секвенирования делают это выражение корректным:

и разрешение было ударить по префиксному примеру и вместо этого использовать пример postfix, который явно undefined:

Измените пример в пункте 1.9 [intro.execution] 16 следующим образом:

i = ++ i я ++ + 1;//поведение undefined

Ответ 2

Но почему i = ++i + 1 undefined в соответствии с терминологией C11?

C11 говорит, что побочный эффект слева i секвентирован, но не вычисляет значения (оценки) левого и правого i.
Очевидно, что побочный эффект на LHS будет иметь место после оценки выражений на LHS и RHS.
Чтобы объяснить это, лучшим примером может быть

int i = 1;
i = i++ + 3;

(Сначала допустим, что этот пример не будет вызывать UB). Теперь конечное значение i может быть 4 или 2.
Случай 1.
Оставлено i, а затем оно увеличивается и 3 добавляется к нему, и, наконец, 4 присваивается i.
Случай 2.
Оставлено i, а затем добавляется 3, а затем 4 присваивается i и, наконец, i увеличивается. В этом случае конечное значение i равно 2.
Хотя побочный эффект на левом i секвенирован, окончательное значение, сохраненное в i, не определено, то есть это не обязательно по заданию, и, следовательно, побочный эффект на i не имеет последствий.

Ответ 3

Стандарт предусматривает назначение (6.5.16), поскольку вы правильно цитируете

Побочным эффектом обновления сохраненного значения левого операнда является упорядочивается после вычисления значений левого и правого операндов.

(Оператор приращения не отличается, это просто переопределение)

Это означает, что есть два вычисления значений (влево и вправо), после чего после них последовательно упорядочивается побочный эффект присваивания. Но он только упорядочен по отношению к вычислениям значений, а не к побочным эффектам, которые они могут иметь. Поэтому в конце мы сталкиваемся с двумя побочными эффектами (оператора = и оператора ++), которые не являются последовательностью относительно друг друга.