c ++ 11 Порядок оценки (неопределенное поведение) - PullRequest
0 голосов
/ 30 октября 2018
vec[ival++] <= vec[ival]

Это выражение имеет неопределенное поведение, поскольку порядок вычисления операндов оператора (<=) не определен. </p>

Как мы можем переписать это выражение, чтобы избежать неопределенного поведения? Я нашел ответ, который, кажется, работает:

vec[ival] <= vec[ival + 1]

Если это правильный способ избежать неопределенного поведения, почему переписывание этого способа позволяет избежать неопределенного поведения?

Было бы замечательно добавить любую ссылку о том, как исправить это выражение.

Ответы [ 4 ]

0 голосов
/ 30 октября 2018

Первая проблема заключается в том, что, поскольку исходный код демонстрирует неопределенное поведение, в стандарте C ++ нет «исправления». Поведение этой строки кода не определено стандартом C ++; чтобы знать, что он должен делать, у вас должен быть другой источник информации.

Формально это выражение можно переписать как system("format c:"), так как стандарт C ++ не предписывает какого-либо поведения из программы, которая демонстрирует неопределенное поведение.

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

Ну, с лямбдами можно что-нибудь решить.

[&]{ bool ret = vec[ival] <= vec[ival+1]; ++ival; return ret; }()

Во-вторых,

vec[ival] <= vec[ival+1]

- это не то же самое, потому что в нем отсутствует побочный эффект: ival больше на 1 после вычисления выражения.

0 голосов
/ 30 октября 2018

Давайте сначала начнем с наихудшей проблемы, а именно с неопределенным поведением. C ++ использует последовательность правила. Заявления выполняются последовательно. Обычно это сверху вниз, но операторы if, операторы for, вызовы функций и т. П. Могут изменить это.

Теперь, с утверждением, еще может быть дальнейшая последовательность выполнения, но я очень намеренно пишу может . По умолчанию различные части одного оператора не упорядочены друг относительно друга. Вот почему вы можете получить различный порядок исполнения. Но что еще хуже, если вы измените и используете один объект без последовательности, у вас будет неопределенное поведение. Это плохо - все может случиться.

Предлагаемое решение (iVal + 1) больше не меняет iVal, но генерирует новое значение. Это совершенно безопасно. Тем не менее, он оставляет iVal без изменений.

Вы можете проверить std::adjacent_find(). Скорее всего, цикл, который вы пытаетесь написать, уже находится в стандартной библиотеке.

0 голосов
/ 30 октября 2018

Да, ваш первый пример имеет неопределенное поведение, потому что у нас есть неупорядоченная модификация и доступ к ячейке памяти, что является неопределенным поведением. Это описано в черновом стандарте C ++ [intro.execution] p10 ( выделение шахты ):

За исключением отмеченных случаев, оценки операндов отдельных операторов и из подвыражений отдельных выражений нет последовательности . [Примечание: в выражении, которое оценивается более одного раза в течение выполнение программы, непоследовательное и неопределенно упорядоченное оценки его подвыражений не должны выполняться последовательно в разных оценках. - примечание конца] Расчет стоимости операнды оператора секвенируются до вычисления значения результат оператора. Если побочный эффект в ячейке памяти ([intro.memory]) не секвенируется относительно любого другого побочного эффекта в той же ячейке памяти или вычислении значения с использованием значения любой объект в той же области памяти, и они не являются потенциально одновременно ([intro.multithread]), поведение не определено . [ Заметка: Следующий подпункт налагает аналогичные, но более сложные ограничения на потенциально параллельные вычисления. - примечание конца] [Пример:

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
}

- конец примера]

Если мы проверим раздел реляционных операторов, который охватывает <= [expr.rel] , он не определяет порядок вычисления, поэтому мы покрыты intro.exection и, таким образом, мы не определили поведение.

Наличие неопределенного порядка вычисления недостаточно для неопределенного поведения, как показано в примере в Порядок вычисления оператора присваивания в C ++ .

Ваш второй пример позволяет избежать неопределенного поведения, поскольку вы не вводите побочный эффект для ival, вы просто читаете ячейку памяти дважды.

Я считаю, что это разумный способ решения проблемы, он читабелен и не удивителен. Альтернативный способ может включать введение второй переменной, например, index и prev_index. Трудно прийти с быстрым правилом, учитывая такой небольшой фрагмент кода.

0 голосов
/ 30 октября 2018

Это позволяет избежать неопределенного поведения, поскольку вы не меняете значение ival. Проблема, которую вы видите в первом примере, заключается в том, что мы не можем определить, какие значения ival используются в то время, когда они используются. Во втором примере нет путаницы.

...