Вычисления без последствий (ака последовательности)
Извините за то, что снова открыла эту тему, но размышление об этой теме уже начало давать мне неопределенное поведение. Хотите переместиться в зону четко определенного поведения.
Данный
int i = 0; int v[10]; i = ++i; //Expr1 i = i++; //Expr2 ++ ++i; //Expr3 i = v[i++]; //Expr4
Я думаю о вышеупомянутых выражениях (в указанном порядке) как
- В C ++ 11, имеет ли `i + = ++ i + 1` неопределенное поведение?
- Одномерный доступ к многомерному массиву: хорошо ли оно определено?
- Является ли stl-вектор одновременным чтением streamобезопасным?
- Что говорит стандарт о том, как определение четкости вектора меняет емкость?
- Является ли std :: abs (0u) плохо сформированным?
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 и значения fetchinga, ранее назначенного объекту для оценки rvalue), так и инициирование побочных эффектов «.
$ 1.9 / 15- «Если побочный эффект на скалярном объекте не влияет на какой-либо другой побочный эффект на один и тот же скалярный объект или вычисление значения с использованием значения одного и того же скалярного объекта, поведение не определено».
[Примечание. Вычисления значений и побочные эффекты, связанные с разными выражениями аргументов, не имеют никакого значения. -End note]
$ 3.9 / 9- «Арифметические типы (3.9.1), типы перечислений, типы указателей, указатель на типы членов (3.9.2), std :: nullptr_t и cv-квалификационные версии этих типов (3.9.3) скалярных типов “.
-
В Expr1 оценка выражения
i
(первый аргумент) не влияет на оценкуoperator++(i)
expessionoperator++(i)
(который имеет побочный эффект).Следовательно, Expr1 имеет неопределенное поведение.
-
В Expr2 оценка выражения
i
(первый аргумент) не влияет на оценкуoperator++(i, 0)
expessionoperator++(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, и используются для других целей. «Это просто фиктивные объявления, не существует семантики функциональных вызовов в отношении встроенных операторов».
- "Inline" ключевое слово vs "inlining" concept
- int a = {1,2,}; Разрешена странная запятая. Любая конкретная причина?
- При использовании заголовков C в C ++ следует ли использовать функции из std :: или глобального пространства имен?
- Имеет ли printf ("% x", 1) неопределенное поведение?
- Возьмите адрес элемента массива «один конец прошлого» через индекс: легальный по стандарту C ++ или нет?
- Когда это действительно для доступа к указателю на «мертвый» объект?
- Является ли сдвиг слева (<<) отрицательным целым неопределенным поведением в C ++ 11?
- «Создание» объекта с возможностью копирования с возможностью memcpy
Собственные операторные выражения не эквивалентны перегруженным операторным выражениям. Существует точка последовательности при привязке значений к аргументам функции, что делает версии 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:
Спасибо Johannes за то, что он справился с моей лень, чтобы напечатать «упорядоченный» в моей панели поиска PDF-файлов. Я собирался спать и вставать на последних двух изменениях в любом случае … правильно; v).
§5.17 / 1, определяющие операторы присваивания
Во всех случаях назначение упорядочивается после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания.
Кроме того, §5.3.2 / 1 о предъявлении оператора
Если 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 (aka адрес) i
неоднозначно зависит от ++i
. Значение glval – это значение i
, но я не думаю, что 1.9 / 15 должен включать его по той простой причине, что glvalue именованного объекта является константой и на самом деле не может иметь зависимости.
Для информативного соломона подумайте
( i % 2? i : j ) = ++ i; // certainly undefined
Здесь glvalue LHS of =
зависит от побочного эффекта на prvalue i
. Адрес i
не подлежит сомнению; результат ?:
есть.
Возможно, хороший контрпример
int i = 3, &j = i; j = ++ i;
Здесь j
имеет glvalue, отличное от (но идентичного) i
. Является ли это четко определенным, но i = ++i
нет? Это представляет собой тривиальное преобразование, которое компилятор может применить к любому случаю.
1.9 / 15 следует сказать
Если побочный эффект на скалярном объекте не влияет на какой-либо другой побочный эффект на один и тот же скалярный объект или вычисление значения с использованием prvalue того же скалярного объекта, поведение не определено.
Рассматривая выражения, подобные упомянутым, я считаю, что полезно представить машину, где память имеет блокировки, так что чтение места памяти как части последовательности чтения-модификации-записи приведет к попыткам чтения или записи, кроме заключительной записи последовательность, которая будет остановлена до тех пор, пока последовательность не завершится. Такая машина вряд ли была бы абсурдной концепцией; действительно, такая конструкция может упростить многие многопоточные сценарии кода. С другой стороны, выражение типа «x = y ++;» может выйти из строя на такой машине, если «x» и «y» были ссылками на одну и ту же переменную, а сгенерированный код компилятора сделал что-то вроде read-and-lock reg1 = y; reg2 = REG1 + 1; написать x = reg1; write-and-unlock y = reg2. Это было бы очень разумной кодовой последовательностью для процессоров, где запись вновь вычисленного значения налагала бы задержку конвейера, но запись в x блокировала бы процессор, если бы у были схожими с одной и той же переменной.