vec[ival++] <= vec[ival]
Это выражение имеет неопределенное поведение, поскольку порядок вычисления операндов оператора (<=) не определен.
Как мы можем переписать это выражение, чтобы избежать неопределенного поведения? Я нашел ответ, который, похоже, работает:
vec[ival] <= vec[ival + 1]
Если это правильный способ избежать неопределенного поведения, почему его переписывание таким образом позволяет избежать неопределенного поведения?
Было бы здорово добавить любую ссылку о том, как исправить это выражение.
Кстати, вы просто выбрали случайную версию C++ или вы конкретно имеете в виду версию C++ с двумя версиями назад.
@MSalters Я ненавижу то, как некоторые вопросы помечаются версией языка или инструмента без какой-либо конкретной причины. Теги версий все еще могут быть полезны, но их нужно использовать по четко определенной причине.
Как я цитирую ниже, он не определен, потому что у вас есть несвязанный побочный эффект и чтение одной и той же области памяти. У вас может быть неопределенный порядок оценки, но не неопределенное поведение этот вопрос - отличный пример
@MSalters Использование C++ 11 даже сейчас не совсем безумие
Прежде чем переписывать его, вы должны определить, чего пытался достичь первоначальный автор. Не очевидно, что они пытались сделать, поэтому есть несколько способов написать это, каждый с разными результатами.





Это позволяет избежать неопределенного поведения, потому что вы не меняете значение ival. Проблема, которую вы видите в первом примере, заключается в том, что мы не можем определить, каковы значения ival в то время, когда они используются. Во втором примере путаницы нет.
Начнем сначала с наихудшей проблемы, а именно с неопределенного поведения. C++ использует правила последовательность действий. Заявления выполняются последовательно. Обычно это сверху вниз, но операторы if, операторы for, вызовы функций и т.п. могут это изменить.
Теперь с оператором все еще может быть дальнейшая последовательность выполнения, но я очень намеренно пишу мог бы. По умолчанию различные части одного оператора не упорядочены относительно друг друга. Вот почему вы можете получить различный порядок исполнения. Но что еще хуже, если вы изменить и использовать один объект без упорядочивания, у вас будет неопределенное поведение. Это плохо - всякое бывает.
Предлагаемое решение (iVal + 1) больше не изменяет iVal, но генерирует новое значение. Это совершенно безопасно. Тем не менее, он оставляет iVal без изменений.
Вы можете проверить std::adjacent_find(). Скорее всего, цикл, который вы пытаетесь написать, уже находится в стандартной библиотеке.
Да, ваш первый пример имеет неопределенное поведение, потому что у нас есть неупорядоченная модификация и доступ к области памяти, что является неопределенным поведением. Это описано в проекте стандарта C++ [intro.execution] стр. 10 (акцент мой):
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [ Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. — end note ] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a memory location ([intro.memory]) is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location, and they are not potentially concurrent ([intro.multithread]), the behavior is undefined. [ Note: The next subclause imposes similar, but more complex restrictions on potentially concurrent computations. — end note ] [ Example:
void g(int i) { i = 7, i++, i++; // i becomes 9 i = i++ + 1; // the value of i is incremented i = i++ + i; // the behavior is undefined i = i + 1; // the value of i is incremented }— end example ]
Если мы проверим раздел реляционных операторов, который охватывает <=[expr.rel], он не определяет порядок оценки, поэтому мы охватываемся intro.exection и, следовательно, имеем неопределенное поведение.
Неопределенного порядка оценки недостаточно для неопределенного поведения, как демонстрирует пример в Порядок оценки оператора присваивания в C++.
Ваш второй пример избегает неопределенного поведения, поскольку вы не вводите побочный эффект для ival, вы просто дважды читаете ячейку памяти.
Я считаю, что это разумный способ решения проблемы, он читабельный и неудивительный. Альтернативный метод может включать введение второй переменной, например index и prev_index. С таким маленьким фрагментом кода трудно придумать быстрое правило.
Первая проблема заключается в том, что, поскольку исходный код демонстрирует неопределенное поведение, в стандарте C++ нет «исправления». Поведение этой строки кода не определяется стандартом C++; чтобы знать, что это должно делать, вам нужен другой источник информации.
Формально это выражение можно переписать как system("format c:"), поскольку стандарт C++ не требует какого-либо поведения от программы, которая демонстрирует неопределенное поведение.
Но на практике, когда вы сталкиваетесь с чем-то подобным, вы должны читать мысли оригинального программиста.
Что ж, вы можете решить все, что угодно, с помощью лямбд.
[&]{ bool ret = vec[ival] <= vec[ival+1]; ++ival; return ret; }()
Второй,
vec[ival] <= vec[ival+1]
это не то же самое, потому что ему не хватает побочного эффекта ival, увеличивающегося на 1 после оценки выражения.
Если ival является нормальным целочисленным типом, я думаю, что такое же поведение может быть достигнуто без лямбда-выражений через (++ival, vec[ival-1] <= vec[ival]).
@supercat за счет использования оператора ,, что для меня слишком далеко
Здесь вы упускаете две концепции. Порядок оценки - это неопределенные. Однако поведение неопределенные по-прежнему ограничено тем, что позволяет язык. Здесь существует только конечное количество порядков, и фактический порядок должен быть одним из них. Неопределенное поведение намного хуже. Язык не накладывает на это ограничений.