Прежде чем вы начнете кричать неопределенное поведение, это явно указано в N4659 (C ++ 17)
i = i++ + 1; // the value of i is incremented
Еще в N3337 (C ++ 11)
i = i++ + 1; // the behavior is undefined
Что изменилось?
Из того, что я могу собрать, из [N4659 basic.exec]
За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными. [...] Вычисления значений операндов оператора секвенируются до вычисления значения результата оператора. Если побочный эффект в области памяти не секвенирован относительно другого побочного эффекта в той же области памяти или вычисления значения с использованием значения любого объекта в той же области памяти, и они не являются потенциально одновременными, поведение не определено.
Где значение определено в [N4659 basic.type]
Для тривиально копируемых типов представление значения представляет собой набор битов в представлении объекта, который определяет значение , которое является одним дискретным элементом набора значений, определенного реализацией.
За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными. [...] Вычисления значений операндов оператора секвенируются до вычисления значения результата оператора. Если побочный эффект на скалярный объект не секвенирован относительно другого побочного эффекта на тот же скалярный объект или вычисления значения с использованием значения того же скалярного объекта, поведение не определено.
Аналогично, значение определяется в [N3337 basic.type]
Для тривиально копируемых типов представление значения представляет собой набор битов в представлении объекта, который определяет значение , которое является одним дискретным элементом определенного реализацией набора значений.
Они идентичны, за исключением упоминания параллелизма, который не имеет значения, и с использованием области памяти вместо скалярного объекта , где
Арифметические типы, типы перечисления, типы указателей, указатели на типы элементов
std::nullptr_t
и cv-квалифицированные версии этих типов вместе называются скалярными типами.
Что не влияет на пример.
Оператор присваивания (=) и составные операторы присваивания все группы справа налево. Все они требуют изменяемого lvalue в качестве своего левого операнда и возвращают lvalue, ссылаясь на левый операнд. Результатом во всех случаях является битовое поле, если левый операнд является битовым полем. Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и до вычисления значения выражения присваивания. Правый операнд упорядочен перед левым операндом.
Оператор присваивания (=) и составные операторы присваивания все группы справа налево. Все они требуют изменяемого lvalue в качестве своего левого операнда и возвращают lvalue, ссылаясь на левый операнд. Результатом во всех случаях является битовое поле, если левый операнд является битовым полем. Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и до вычисления значения выражения присваивания.
Единственное отличие состоит в том, что последнее предложение отсутствует в N3337.
Последнее предложение, однако, не должно иметь никакого значения, так как левый операнд не i
является ни «другим побочным эффектом», ни «использованием значения того же скалярного объекта», поскольку id-выражение является lvalue.
источник
i = i++ + 1;
.Ответы:
В C ++ 11 действие «присваивания», то есть побочный эффект изменения LHS, секвенируется после вычисления значения правого операнда. Обратите внимание, что это относительно «слабая» гарантия: она производит секвенирование только в отношении вычисления значения RHS. В нем ничего не говорится о побочных эффектах, которые могут присутствовать в RHS, поскольку возникновение побочных эффектов не является частью вычисления значения . Требования C ++ 11 не устанавливают никакой относительной последовательности между актом уступки и какими-либо побочными эффектами RHS. Это то, что создает потенциал для UB.
Единственная надежда в этом случае - какие-либо дополнительные гарантии, предоставляемые конкретными операторами, используемыми в RHS. Если бы RHS использовал префикс
++
, свойства последовательности, специфичные для формы префикса++
, спасли бы день в этом примере. Но постфикс++
- это другая история: он не дает таких гарантий. В C ++ 11 побочные эффекты=
и postfix++
оказываются неупорядоченными по отношению друг к другу в этом примере. И это UB.В C ++ 17 добавлено дополнительное предложение к спецификации оператора присваивания:
В сочетании с вышесказанным это дает очень сильную гарантию. Он упорядочивает все, что происходит в RHS (включая любые побочные эффекты), прежде чем все, что происходит в LHS. Поскольку фактическое назначение упорядочено после LHS (и RHS), такое дополнительное упорядочение полностью изолирует акт назначения от любых побочных эффектов, присутствующих в RHS. Это более сильное секвенирование - это то, что устраняет вышеуказанный UB.
(Обновлено с учетом комментариев @Джона Боллинджера.)
источник
x -= y;
обрабатывать что-то вроде,mov eax,[y] / sub [x],eax
а неmov eax,[x] / neg eax / add eax,[y] / mov [x],eax
. Я не вижу в этом ничего идиотского. Если нужно было указать порядок, наиболее эффективный порядок, вероятно, заключался бы в том, чтобы сначала выполнить все вычисления, необходимые для идентификации левого объекта, а затем оценить правый операнд, а затем значение левого объекта, но для этого потребовалось бы иметь термин для акта разрешения идентификатора левого объекта.x
иy
былоvolatile
, это имело бы побочные эффекты. Кроме того, те же соображения будут применяться кx += f();
, гдеf()
изменяетсяx
.Вы определили новое предложение
и вы правильно определили, что оценка левого операнда как lvalue не имеет значения. Тем не менее, секвенированный ранее определен как транзитивное отношение. Полный правый операнд (включая постинкремент), следовательно, также упорядочивается перед назначением. В C ++ 11 до вычисления присваивалось только вычисление значения правого операнда.
источник
В более старых стандартах C ++ и в C11 определение текста оператора присваивания заканчивается текстом:
Это означает, что побочные эффекты в операндах не являются последовательными и, следовательно, определенно неопределенным поведением, если они используют одну и ту же переменную.
Этот текст был просто удален в C ++ 11, что делает его несколько двусмысленным. Это UB или нет? Это было разъяснено в C ++ 17, где они добавили:
Как примечание, в еще более старых стандартах все это было очень ясно показано, например, на C99:
В основном, в C11 / C ++ 11 они запутались, когда удалили этот текст.
источник
Это дополнительная информация к другим ответам, и я выкладываю ее, так как часто задают следующий код .
Объяснение в других ответах является правильным и также относится к следующему коду, который теперь четко определен (и не меняет хранимое значение
i
):Это
+ 1
красная сельдь, и не совсем понятно, почему Стандарт использовал ее в своих примерах, хотя я помню, что люди спорили в списках рассылки до C ++ 11, что, возможно, это+ 1
имело значение из-за принудительного раннего преобразования lvalue справа. сторона Конечно, ничего из этого не применимо в C ++ 17 (и, вероятно, никогда не применяется ни в одной версии C ++).источник