c++ - точки - Расчеты с неупорядоченными значениями(точками последовательности)




точки следования c++ (2)

Размышляя о выражениях, подобных упомянутым, я считаю полезным представить себе машину, в которой память имеет блокировки, так что чтение области памяти как части последовательности чтения-изменения-записи вызовет любую попытку чтения или записи, кроме заключительной записи последовательность, которая будет остановлена, пока последовательность не завершится. Такая машина вряд ли была бы абсурдной концепцией; действительно, такой дизайн может упростить многие сценарии многопоточного кода. С другой стороны, выражение типа "x = y ++;" может произойти сбой на такой машине, если 'x' и 'y' были ссылками на одну и ту же переменную, а сгенерированный код компилятора сделал что-то вроде read-and-lock reg1 = y; reg2 = REG1 + 1; напишите x = reg1; запись и разблокировка y = reg2. Это было бы очень разумной последовательностью кода для процессоров, где запись недавно вычисленного значения наложила бы конвейерную задержку, но запись в x блокировала бы процессор, если бы y был привязан к той же самой переменной.

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

Дано

int i = 0;
int v[10];
i = ++i;     //Expr1
i = i++;     //Expr2
++ ++i;      //Expr3
i = v[i++];  //Expr4

Я думаю о приведенных выше выражениях (в таком порядке) как

operator=(i, operator++(i))    ; //Expr1 equivalent
operator=(i, operator++(i, 0)) ; //Expr2 equivalent
operator++(operator++(i))      ; //Expr3 equivalent
operator=(i, operator[](operator++(i, 0)); //Expr4 equivalent

Теперь перейдем к поведению здесь важные цитаты из C ++ 0x .

$ 1.9 / 12- «Оценка выражения (или подвыражения) в общем случае включает в себя как вычисления значения (включая определение идентичности объекта для оценки lvalue и значения извлечения, ранее назначенного объекту для оценки rvalue), так и инициирование побочных эффектов «.

$ 1.9 / 15- «Если побочный эффект на скалярный объект не секвенирован относительно другого побочного эффекта на тот же скалярный объект или вычисления значения с использованием значения того же скалярного объекта, поведение не определено».

[Примечание: вычисления значений и побочные эффекты, связанные с различными выражениями аргументов, не являются последовательными. —Конечная записка]

$ 3.9 / 9- "Арифметические типы (3.9.1), перечислимые типы, типы указателей, указатели на типы членов (3.9.2), std :: nullptr_t и cv-квалифицированные версии этих типов (3.9.3) совместно называются скалярные типы. "

  • В Expr1 вычисление выражения i (первый аргумент) является неупорядоченным по отношению к вычислению operator++(i) выражения operator++(i) (который имеет побочный эффект).

    Следовательно, Expr1 имеет неопределенное поведение.

  • В Expr2 вычисление выражения i (первый аргумент) не выполняется по отношению к вычислению operator++(i, 0) expession operator++(i, 0) (который имеет побочный эффект) '.

    Следовательно, Expr2 имеет неопределенное поведение.

  • В Expr3 оценка operator++(i) одиночного аргумента operator++(i) должна быть завершена до вызова внешнего operator++ .

    Следовательно, Expr3 имеет четко определенное поведение.

  • В Expr4 вычисление выражения i (первый аргумент) не секвенируется относительно вычисления operator[](operator++(i, 0) (который имеет побочный эффект).

    Следовательно, Expr4 имеет неопределенное поведение.

Это понимание правильно?

PS Метод анализа выражений, как в OP, не является правильным. Это связано с тем, что, как отмечает @Potatoswatter, пункт 13.6 не применяется. См. Заявление об отказе от ответственности в разделе 13.6 / 1 «Эти функции-кандидаты участвуют в процессе разрешения перегрузки оператора, как описано в 13.3.1.2, и не используются ни для каких других целей. «Это просто фиктивные объявления; семантики вызовов функций по отношению к встроенным операторам не существует».


Собственные операторные выражения не эквивалентны перегруженным операторным выражениям. Существует точка последовательности при привязке значений к аргументам функции, что делает версии operator++() четко определенными. Но это не существует для случая нативного типа.

Во всех четырех случаях i меняюсь дважды в пределах полного выражения. Так как нет, || , или && появляются в выражениях, это мгновенно UB.

§ 5/4:

Между предыдущей и следующей точкой последовательности скалярному объекту должно быть изменено его сохраненное значение не более одного раза путем оценки выражения.

Редактировать для C ++ 0x (обновлено)

§1.9 / 15:

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

Однако обратите внимание, что вычисление значения и побочный эффект - это две разные вещи. Если ++i эквивалентно i = i+1 , то + является вычислением значения, а = является побочным эффектом. От 1.9 / 12:

Оценка выражения (или подвыражения) в целом включает в себя как вычисления значений (включая определение идентичности объекта для оценки glvalue и выборку значения, ранее назначенного объекту для оценки prvalue), так и инициирование побочных эффектов.

Таким образом, хотя вычисления значений более последовательны в C ++ 0x, чем в C ++ 03, побочные эффекты - нет. Два побочных эффекта в одном и том же выражении, если не секвенировано иное, вызывают UB

Вычисления значений в любом случае упорядочены по их зависимостям данных, и, если побочные эффекты отсутствуют, порядок их оценки ненаблюдаем, поэтому я не уверен, почему C ++ 0x пытается что-то сказать, но это просто означает, что мне нужно больше читать из бумаг Бём и его друзья написали.

Редактировать № 3:

Спасибо Йоханнесу за то, что он справился с моей ленью, чтобы набрать «секвенированный» в моей строке поиска PDF-ридер В любом случае я ложился спать и вставал с последними двумя правками… правильно; v).

§5.17 / 1 определяет операторы присваивания

Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и до вычисления значения выражения присваивания.

Также §5.3.2 / 1 об операторе preincrement говорит

Если x не относится к типу bool, выражение ++ x эквивалентно x + = 1 [Примечание: см.… Сложение (5.7) и операторы присваивания (5.17)…].

По этой идентичности ++ ++ x является сокращением для (x +=1) +=1 . Итак, давайте интерпретировать это.

  • Оцените 1 на дальнем RHS и спускайтесь в Parens.
  • Оцените внутренний 1 и значение (prvalue) и адрес (glvalue) x .
  • Теперь нам нужно значение подвыражения + =.
    • Мы закончили вычисления значений для этого подвыражения.
    • Побочный эффект присваивания должен быть упорядочен до того, как будет доступно значение присваивания!
  • Присвойте новое значение x , которое совпадает с результатом glvalue и prvalue подвыражения.
  • Мы из леса сейчас. Все выражение теперь уменьшено до x +=1 .

Итак, тогда 1 и 3 четко определены, а 2 и 4 - неопределенное поведение, которое вы ожидаете.

Единственный другой сюрприз, который я обнаружил, выполнив поиск "sequenced" в N3126, был 5.3.4 / 16, где реализация позволяет вызывать operator new до оценки аргументов конструктора. Это круто.

Редактировать # 4: (О, какая запутанная паутина, которую мы плетем)

Йоханнес снова отмечает, что в i == ++i; glvalue (он же адрес) i неоднозначно зависит от ++i . Значение glvalue, безусловно, является значением i , но я не думаю, что 1.9 / 15 предназначено для его включения по той простой причине, что glvalue именованного объекта является константой и фактически не может иметь зависимостей.

Для информативного соломника, рассмотрим

( i % 2? i : j ) = ++ i; // certainly undefined

Здесь glvalue LHS of = зависит от побочного эффекта на значение i . Адрес i не в вопросе; результат ?: есть.

Возможно, хороший контрпример

int i = 3, &j = i;
j = ++ i;

Здесь j имеет значение, отличное от (но идентичное) i . Это хорошо определено, но i = ++i нет? Это представляет собой тривиальное преобразование, которое компилятор может применить к любому случаю.

1.9 / 15 следует сказать

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





sequence-points