Расчеты с неупорядоченными значениями (точками последовательности)
Извините, что снова открыл эту тему, но размышления над этой темой сами по себе начали давать мне неопределенное поведение. Хочу перейти в зону четко определенного поведения.
Дано
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)
(который имеет побочный эффект).Следовательно, Expr1 имеет неопределенное поведение.
В Expr2, оценка выражения
i
(первый аргумент), не является последовательным в отношении оценки выраженияoperator++(i, 0)
(что имеет побочный эффект).Следовательно, Expr2 имеет неопределенное поведение.
В Expr3 оценка одинокого аргумента
operator++(i)
должен быть завершен до внешнегоoperator++
называется.Следовательно, Expr3 имеет четко определенное поведение.
В Expr4, оценка выражения
i
(первый аргумент) не является последовательным в отношении оценкиoperator[](operator++(i, 0)
(который имеет побочный эффект).Следовательно, Expr4 имеет неопределенное поведение.
Это понимание правильно?
PS Метод анализа выражений, как в OP, не верен. Это связано с тем, что, как отмечает @Potatoswatter, пункт 13.6 не применяется. См. Заявление об отказе от ответственности в разделе 13.6/1 "Эти функции-кандидаты участвуют в процессе разрешения перегрузки оператора, как описано в 13.3.1.2, и не используются ни для каких других целей."Это просто фиктивные объявления; никакой семантики вызова функций по отношению к встроенным операторам не существует".
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 Reader В любом случае я ложился спать и вставал с последними двумя правками… верно; 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 - неопределенное поведение, которое вы ожидаете.
Единственный другой сюрприз, который я обнаружил при поиске "секвенированного" в 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
Здесь, блеск LHS =
зависит от побочного эффекта на prvalue i
, Адрес i
не под вопросом; результат ?:
является.
Возможно, хороший контрпример
int i = 3, &j = i;
j = ++ i;
Вот j
имеет значение, отличное от (но идентичное) i
, Это четко определено, но i = ++i
не является? Это представляет тривиальное преобразование, которое компилятор может применить к любому случаю.
1.9/15 следует сказать
Если побочный эффект на скалярный объект не секвенирован относительно другого побочного эффекта на тот же скалярный объект или вычисления значения с использованием значения prvalue того же скалярного объекта, поведение не определено.
Размышляя о выражениях, подобных упомянутым, я считаю полезным представить себе машину, в которой память имеет блокировки, так что чтение области памяти как части последовательности чтения-изменения-записи вызовет любую попытку чтения или записи, кроме заключительной записи последовательность, которая будет остановлена, пока последовательность не завершится. Такая машина вряд ли была бы абсурдной концепцией; действительно, такой дизайн может упростить многие сценарии многопоточного кода. С другой стороны, выражение типа "x=y++;" может произойти сбой на такой машине, если 'x' и 'y' были ссылками на одну и ту же переменную, а сгенерированный код компилятора сделал что-то вроде read-and-lock reg1=y; reg2= REG1+1; напишите x=reg1; запись и разблокировка y=reg2. Это было бы очень разумной последовательностью кода для процессоров, где запись недавно вычисленного значения наложила бы конвейерную задержку, но запись в x блокировала бы процессор, если бы y был псевдонимом к той же самой переменной.