Последовательность операторов присваивания в выражениях C11
Вступление
Стандарт C11 (ISO/IEC 9899:2011) ввел новое определение последовательности побочных эффектов в выражении ( см. Связанный вопрос). Концепция точки последовательности была дополнена последовательностью до и последовательностью после отношений, которые теперь являются основой для всех определений.
Раздел 6.5 "Выражения", пункт 2 гласит:
Если побочный эффект для скалярного объекта не секвенирован относительно другого побочного эффекта для того же скалярного объекта или вычисления значения с использованием значения того же скалярного объекта, поведение не определено. Если существует несколько допустимых порядков подвыражений выражения, поведение не определено, если такой беспорядочный побочный эффект возникает в любом из порядков.
Далее в разделе 6.5.16 "Операторы присваивания", пункт 3, говорится:
Побочный эффект обновления сохраненного значения левого операнда упорядочен после вычислений значения левого и правого операнда. Оценки операндов не являются последовательными.
проблема
Первый цитируемый абзац (6.5/2) подтверждается двумя примерами (такими же, как в стандарте C99):
Первый пример
a[i++] = i; //! undefined
a[i] = i; // allowed
Это можно легко объяснить с помощью определений:
- Если побочный эффект на скалярный объект не секвенирован относительно (...) вычисления значения с использованием значения того же скалярного объекта, поведение не определено. (6,5/2),
- Оценки операндов не являются последовательными. [в рамках задания] (6.5.16/3).
Итак, побочный эффект i++
(LHS) не секвенируется с i
(RHS), который дает неопределенное поведение.
Второй пример
i = ++i + 1; //! undefined
i = i + 1; // allowed
Этот код, однако, похоже, приводит к определенному поведению в обоих случаях:
- побочный эффект обновления сохраненного значения левого операнда секвенируется после вычислений значения левого и правого операнда.
Итак, исполнение ++i + 1
должен предшествовать побочный эффект обновления i
Это означает, что не существует побочного эффекта для скалярного объекта, не секвенированного относительно другого побочного эффекта для того же скалярного объекта или вычисления значения с использованием значения того же скалярного объекта.
Вопрос
Эти примеры легко объяснить с помощью терминов и определений, представленных в стандарте C99 ( см. Соответствующий вопрос). Но почему i = ++i + 1
не определено в соответствии с терминологией C11?
3 ответа
Обновить
Я изменяю свой ответ здесь, это не очень хорошо определено в C11, хотя это в C++11. Ключевым моментом здесь является то, что результат ++i
не является lvalue и, следовательно, не требует преобразования lvalue в rvalue после ++i
оценивается и поэтому мы не можем быть уверены, что результат ++i
будет прочитано позже. Это отличается от C++, и поэтому отчет о дефектах, который я изначально связал, зависит от этого важного факта:
[...] выражение lvalue ++i, а затем выполнить преобразование lvalue в rvalue для результата. гарантирует, что побочный эффект увеличения секвенируется перед вычислением операции сложения [...]
мы можем увидеть это, перейдя в раздел проекта стандарта C11 6.5.3.1
Префиксный оператор увеличения и уменьшения, который говорит:
[...] Выражение ++E эквивалентно (E+=1).[...]
а затем раздел 6.5.16
Операторы присваивания, которые говорят (выделение мое в будущем):
Оператор присваивания сохраняет значение в объекте, обозначенном левым операндом. Выражение присваивания имеет значение левого операнда после присваивания111, но не является lvalue. [...]
и сноска 111
говорит:
Реализация может считывать объект для определения значения, но не обязательна, даже если объект имеет тип volatile-qualified.
Нет необходимости читать объект, чтобы определить его стоимость, даже если он изменчив.
Оригинальный ответ
Насколько я могу судить, это на самом деле хорошо определено, и этот пример был удален из проекта стандарта C++, который использует похожий язык. Мы можем видеть это в 637. Правила последовательности и пример не согласны, который говорит:
следующее выражение все еще перечислено как пример неопределенного поведения:
i = ++i + 1;
Однако, похоже, что новые правила последовательности делают это выражение четко определенным:
и было решено использовать пример с префиксом и использовать пример с постфиксом, который явно не определен:
Измените пример в пункте 1.9 [intro.execution] следующим образом:
i =
++ii ++ + 1; // поведение не определено
Стандарт предусматривает присвоение (6.5.16), поскольку вы цитируете правильно
Побочный эффект обновления сохраненного значения левого операнда упорядочен после вычислений значения левого и правого операнда.
(Оператор приращения не отличается, это просто скрытое присваивание)
Это означает, что есть два вычисления значения (левое и правое), и побочный эффект назначения затем упорядочивается после них. Но это только последовательность против вычислений значения, а не против побочных эффектов, которые они могут иметь. Таким образом, в конце мы столкнулись с двумя побочными эффектами (из =
оператор и ++
оператор), которые не являются последовательностью по отношению друг к другу.
Но почему
i = ++i + 1
не определено в соответствии с терминологией C11?
С11 говорит, что побочный эффект слева 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
не секвенировано