Расчеты с неупорядоченными значениями (точками последовательности) - PullRequest
16 голосов
/ 04 октября 2010

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

Учитывая

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 оценка expression i (первый аргумент) не упорядочен относительно оценки operator[](operator++(i, 0) (которая имеет побочный эффект).

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

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


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

Ответы [ 2 ]

15 голосов
/ 04 октября 2010

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

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

§5 / 4:

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

Правка для C ++ 0x (обновлено)

§1.9 / 15:

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

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

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

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

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

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

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

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

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

Также в §5.3.2 / 1 оператора прединкремента указано

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

By++ ++ x это сокращение от (x +=1) +=1.Итак, давайте интерпретируем это.

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

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

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

Правка № 4: (О-о, какая запутанная паутина, которую мы плетем)nnes снова отмечает, что в i == ++i; значение glvalue (например, адрес) в i неоднозначно зависит от ++i. Значение glvalue определенно равно a значению i, но я не думаю, что 1.9 / 15 предназначено для его включения по той простой причине, что glvalue именованного объекта является константой и не может фактически иметь зависимости . Для информативного соломника рассмотрим ( i % 2? i : j ) = ++ i; // certainly undefined Здесь glvalue LHS = зависит от побочного эффекта на prvalue i. Адрес i не обсуждается; исход ?: есть. Возможно, хороший контрпример int i = 3, &j = i; j = ++ i; Здесь j имеет glvalue, отличный от (но идентичный) i. Это хорошо определено, но i = ++i нет? Это представляет тривиальное преобразование, которое компилятор может применить к любому случаю. 1,9 / 15 следует сказать Если побочный эффект на скалярном объекте не секвенирован относительно другого побочного эффекта на том же скалярном объекте или вычисления значения с использованием prvalue того же скалярного объекта, поведение не определено.

0 голосов
/ 01 ноября 2011

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

...