Я читал о порядке нарушений оценки , и они приводят пример, который озадачивает меня.
1) Если побочный эффект на скалярный объект не секвенирован относительно другого побочного эффекта на тот же скалярный объект, поведение не определено.
// snip f(i = -1, i = -1); // undefined behavior
В этом контексте i
это скалярный объект , который, по-видимому, означает
Арифметические типы (3.9.1), типы перечисления, типы указателей, указатели на типы элементов (3.9.2), std :: nullptr_t и cv-квалифицированные версии этих типов (3.9.3) вместе называются скалярными типами.
Я не вижу, как это утверждение неоднозначно в этом случае. Мне кажется, что независимо от того, оценивается ли первый или второй аргумент первым, он i
заканчивается как -1
, и оба аргумента также -1
.
Может кто-нибудь уточнить, пожалуйста?
ОБНОВИТЬ
Я действительно ценю все обсуждения. До сих пор мне очень нравится ответ @ damaic, так как он раскрывает подводные камни и тонкости определения этого утверждения, несмотря на то, насколько прямолинейно оно выглядит на первый взгляд. @ acheong87 указывает на некоторые проблемы, которые возникают при использовании ссылок, но я думаю, что это ортогонально аспекту побочных эффектов этого вопроса.
РЕЗЮМЕ
Поскольку этот вопрос получил массу внимания, я суммирую основные моменты / ответы. Во-первых, позвольте мне сделать небольшое отступление, чтобы указать, что «почему» может иметь тесно связанные, но слегка различающиеся значения, а именно «по какой причине », «по какой причине » и «с какой целью ». Я сгруппирую ответы, по которым из этих значений «почему» они обращались.
по какой причине
Основной ответ здесь приходит от Пола Дрейпера , с Мартином Дж, который внес аналогичный, но не столь обширный ответ. Ответ Пола Дрейпера сводится к
Это неопределенное поведение, потому что не определено, что это за поведение.
Ответ в целом очень хороший с точки зрения объяснения того, что говорит стандарт C ++. Также рассматриваются некоторые связанные случаи UB, такие как f(++i, ++i);
и f(i=1, i=-1);
. В первом из связанных случаев не ясно, должен ли первый аргумент быть, i+1
а второй i+2
или наоборот; во-вторых, не ясно, i
должно ли быть 1 или -1 после вызова функции. Оба эти случая являются UB, потому что они подпадают под следующее правило:
Если побочный эффект на скалярный объект не секвенирован относительно другого побочного эффекта на тот же скалярный объект, поведение не определено.
Следовательно, f(i=-1, i=-1)
это также UB, поскольку он подпадает под то же правило, несмотря на то, что намерение программиста является (IMHO) очевидным и однозначным.
Пол Дрэйпер также ясно указывает в своем заключении, что
Могло ли это быть определено поведение? Да. Это было определено? Нет.
что приводит нас к вопросу «по какой причине / цели было f(i=-1, i=-1)
оставлено неопределенное поведение?»
по какой причине / цели
Хотя в стандарте C ++ есть некоторые упущения (возможно, небрежные), многие упущения хорошо обоснованы и служат определенной цели. Хотя я знаю, что цель часто заключается в том, чтобы «облегчить работу автора-компилятора» или «ускорить код», мне было интересно узнать, есть ли веская причина оставить f(i=-1, i=-1)
UB.
вредный и суперкат дают основные ответы, которые дают повод для UB. Harmic отмечает, что оптимизирующий компилятор, который может разбить якобы атомарные операции присваивания на несколько машинных инструкций, и что он может дополнительно чередовать эти инструкции для оптимальной скорости. Это может привести к некоторым очень удивительным результатам: i
в его сценарии он заканчивается как -2! Таким образом, вредный код демонстрирует, как назначение одного и того же значения переменной более одного раза может иметь негативные последствия, если операции не выполняются.
Суперкат представляет собой связное изложение подводных камней, пытающихся добиться того, f(i=-1, i=-1)
на что он похож. Он указывает, что на некоторых архитектурах существуют жесткие ограничения на одновременную запись в один и тот же адрес памяти. Компилятору может быть трудно поймать это, если мы имеем дело с чем-то менее тривиальным, чем f(i=-1, i=-1)
.
Дэвидф также приводит пример инструкций чередования, очень похожих на вредные.
Хотя каждый из примеров вредителей, суперкатов и дэвидфов несколько надуман, вместе взятые они все же служат обоснованной причиной, почему f(i=-1, i=-1)
должно быть неопределенное поведение.
Я принял ответ вредителя, потому что он наилучшим образом справился со всеми смыслами того, почему, хотя в ответе Пола Дрейпера был лучше проработан раздел «по какой причине».
другие ответы
ДжонБ указывает, что если мы рассмотрим перегруженные операторы присваивания (а не просто простые скаляры), то у нас тоже могут возникнуть проблемы.
f(i-1, i = -1)
или что-то подобное.
std::nullptr_t
и cv-квалифицированные версии этих типов (3.9.3) вместе называются скалярными типами . "