Введение
В стандарте 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?
Ответ 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), поскольку вы правильно цитируете
Побочным эффектом обновления сохраненного значения левого операнда является упорядочивается после вычисления значений левого и правого операндов.
(Оператор приращения не отличается, это просто переопределение)
Это означает, что есть два вычисления значений (влево и вправо), после чего после них последовательно упорядочивается побочный эффект присваивания. Но он только упорядочен по отношению к вычислениям значений, а не к побочным эффектам, которые они могут иметь. Поэтому в конце мы сталкиваемся с двумя побочными эффектами (оператора =
и оператора ++
), которые не являются последовательностью относительно друг друга.