Точки последовательности против приоритета оператора

Возможный дубликат:
вычисления непоследовательных значений (иначе точки следования )
Неопределенное поведение и точки последовательности
Приоритет оператора и порядок оценки

Я все еще пытаюсь понять, как следующее выражение приводит к неопределенному поведению:

a = a++;

При поиске SO по этому поводу я нашел следующий вопрос:

Разница между точками следования и приоритетом оператора? 0_о

Я прочитал все ответы, но у меня все еще есть трудности с деталями. Один из ответов описывает поведение моего приведенного выше примера кода как неоднозначное с точки зрения того, как изменяется a. Например, это может сводиться к любому из этих:

a=(a+1);a++;
a++;a=a;

Что именно делает модификацию a неоднозначной? Связано ли это с инструкциями ЦП на разных платформах и как оптимизатор может воспользоваться неопределенным поведением? Другими словами, он кажется неопределенным из-за сгенерированного ассемблера?

Я не вижу причин для компилятора использовать a=(a+1);a++;, это просто выглядит причудливо и не имеет особого смысла. Что могло бы заставить компилятор вести себя таким образом?

РЕДАКТИРОВАТЬ:

Просто чтобы быть ясным, я понимаю, что происходит, я просто не понимаю, как это может быть неопределенным, когда есть правила приоритета операторов (которые, по сути, определяют порядок вычисления выражения). В этом случае присваивание происходит последним, поэтому a++ необходимо оценить первым, чтобы определить значение, которое нужно присвоить a. Итак, я ожидаю, что a сначала модифицируется во время приращения post-fix, но затем дает значение, которое можно присвоить обратно a (вторая модификация). Но правила приоритета операторов, кажется, делают поведение очень понятным для меня, я не могу найти, где есть какое-либо «пространство для маневра», чтобы оно имело неопределенное поведение.


person void.pointer    schedule 07.09.2012    source источник
comment
Вы запрашиваете изменение a дважды без точки следования: один раз в назначении и один раз как побочный эффект ++. Стандарт не указывает, что вы имеете в виду.   -  person Kerrek SB    schedule 07.09.2012
comment
@KerrekSB: я уже знал, что a дважды модифицируется, я спрашиваю, что именно в нем не определено. В стандарте не указано, что вы имеете в виду -- Что? Он указывает порядок операций и приоритет операторов, он очень ясно дает понять, что означает выражение. Я могу смотреть на это и знать, что происходит, по порядку.   -  person void.pointer    schedule 07.09.2012
comment
Приоритет оператора просто устраняет неоднозначность между несколькими возможными способами анализа выражения (подумайте о *p++, что означает (*p)++), он не помогает определить порядок, в котором происходят модификации объекта.   -  person jrok    schedule 07.09.2012
comment
ДА, ребята, есть дубликаты, но что хорошего в дубликатах для меня, если ответы там не имеют для меня никакого смысла? Моя цель - получить разные ответы, чтобы лучше понять.   -  person void.pointer    schedule 07.09.2012
comment
@Robert - Насколько это важно? Вы пытаетесь дважды изменить значение a в одном и том же выражении. Почему вы хотите это сделать? В этом нет абсолютно никакой практической пользы.   -  person Bo Persson    schedule 07.09.2012
comment
@BoPersson Я никогда не говорил, что использую это на практике. Это образовательная сессия, чтобы лучше понять семантику C++, определенную стандартом.   -  person void.pointer    schedule 07.09.2012
comment
@Robert Этот ответ очень помог мне понять это.   -  person jrok    schedule 07.09.2012
comment
@Robert - Но тут нечего понимать. :-) В реальном мире нет необходимости обновлять a дважды, так какая нам разница? ++a будет работать нормально, a = a++ + ++a - a-- - нет. Используйте тот, который работает!   -  person Bo Persson    schedule 07.09.2012
comment
@Robert: Моя цель - получить разные ответы. Затем вы используете bounty, говоря, что вы не понимаете текущие ответы на этот вопрос.   -  person Nicol Bolas    schedule 07.09.2012
comment
Я думаю, вы пытаетесь чрезмерно анализировать ситуацию. У вас есть код, который по правилам языка имеет неопределенное поведение. На этом этапе вы не можете применить какую-либо логику, например, если бы у нее не было неопределенного поведения; он может иметь только одно значение, поэтому он не должен быть неопределенным. Это было это.   -  person CB Bailey    schedule 07.09.2012
comment
@CharlesBailey Одна из величайших философий науки: всегда задавайте вопросы. Всегда есть причина, по которой что-то не определено. Когда комитет решил не вводить правила, которые в противном случае сделали бы его четко определенным, я хочу знать, какие сценарии проигрывались в их голове.   -  person void.pointer    schedule 07.09.2012
comment
Но стандарт программирования — это не естественное явление, к которому можно применить научный метод и рассуждения. Это набор правил, придуманных людьми.   -  person CB Bailey    schedule 07.09.2012
comment
@CharlesBailey А правительство и закон - одно и то же, но только потому, что существуют правила, это не делает их правильными. Точно так же, как я пытаюсь понять, почему у чего-то есть правило, я хочу понять, почему у чего-то нет правила. Вы неправильно меня понимаете, вы думаете, что я пытаюсь изменить стандарт или хочу, чтобы он был другим. Нет. Я пытаюсь понять, почему он не определен. Пожалуйста, проясните это.   -  person void.pointer    schedule 07.09.2012
comment
Хорошо, но когда я ответил на ваш комментарий, он просто гласил: @CharlesBailey Одна из величайших философий о науке: всегда задавайте вопросы.. Я надеюсь, что мой ответ имеет больше смысла в этом контексте.   -  person CB Bailey    schedule 07.09.2012
comment
@CharlesBailey Программирование по-прежнему остается наукой, а наука не всегда означает, что что-то должно быть естественным явлением. Однако C++ - это то, к чему вы, безусловно, можете применить научный метод и аргументацию, и стандарт является доказательством этого (это прямой результат). Кроме того, стандарт не является фиксированной сущностью, он развивается (пример: C++11) из-за научных вопросов (например, как мы можем сделать это лучше?) -- Сейчас я отклоняюсь от темы, но это то, что я имел в виду.   -  person void.pointer    schedule 07.09.2012
comment
@RobertDailey: Если вы посмотрите на мой ответ на вопрос, который я назвал дубликатом, в нем приведены весьма конкретные и (IMO) довольно разумные примеры того, как вы в конечном итоге получите проблемное поведение из-за того типа кода, который вы обсуждали. . Если вы уже рассмотрели это и находите мои рассуждения трудными для понимания (или надуманными), я был бы признателен, если бы услышал об этом. У меня нет проблем с редактированием, если это прояснит ответ.   -  person Jerry Coffin    schedule 07.09.2012


Ответы (6)


Первый ответ на вопрос, на который вы ссылались, точно объясняет, что происходит. Попробую перефразировать, чтобы было понятнее.

Приоритет оператора определяет порядок вычисления значений с помощью выражений. Результат выражения (a++) хорошо понятен.

Однако изменение переменной a не является частью выражения. Да, действительно. Это та часть, которую вам трудно понять, но это просто то, как это определяют C и C++.

Выражения приводят к значениям, но некоторые выражения могут иметь побочные эффекты. Выражение a = 1 имеет значение 1, но оно также имеет побочный эффект, заключающийся в присвоении переменной a значения 1. Что касается определения C и C++, это два разных шага. Точно так же a++ имеет значение и побочный эффект.

Точки последовательности определяют, когда побочные эффекты видны для выражений, которые оцениваются после этих точек последовательности. Приоритет оператора не имеет ничего общего с точками следования. Именно так C/C++ определяет вещи.

person Nicol Bolas    schedule 07.09.2012
comment
Разделение обработки побочных эффектов и оценки выражения — это определенно то, с чем у меня возникли проблемы, потому что у меня просто плохое предчувствие, что существует ситуация, когда значение побочного эффекта зависит от результат выражения, и в этом случае побочные эффекты ДОЛЖНЫ быть обработаны до полной оценки выражения. Побочные эффекты — это просто модификация какого-то блока памяти. Что, если одно и то же выражение работает с этим блоком памяти? Однако я не могу придумать никаких примеров, которые могли бы это сделать. - person void.pointer; 07.09.2012
comment
@RobertDailey: В том-то и дело: если вы пишете выражение, в котором значение, вычисляемое этим выражением, основано на побочных эффектах, которые не упорядочены должным образом, вы получаете неопределенное поведение. Это двухуровневая система: вам нужен приоритет оператора, но исходные значения должны быть упорядочены с учетом любых побочных эффектов от предыдущих выражений. - person Nicol Bolas; 07.09.2012
comment
@Robert Что, если одно и то же выражение работает с этим блоком памяти. Я думаю, в этом суть. C и C++ (к лучшему или к худшему) не определяют порядок, в котором возникают побочные эффекты, но они говорят, когда эти побочные эффекты должны быть полными< /i> (по следующей точке последовательности до C++11, до следующей последовательности после операции в C++11). Следовательно, правило не изменяет что-то дважды между двумя точками последовательности, чтобы предотвратить двусмысленность. - person jrok; 07.09.2012
comment
Зачем делать это неопределенным? Это моя точка зрения. a = a++, почему бы не потребовать, чтобы побочные эффекты обрабатывались по запросу, как только оценка достигает этой точки? Измените a на 1, затем верните 0 и, наконец, присвойте 0 a. Я не вижу причин делать поведение неопределенным. О чем они думали, когда устанавливали это таким образом в стандарте? - person void.pointer; 07.09.2012
comment
@RobertDailey: Почему это неопределенное поведение — это вопрос, отличный от вопроса почему. Последнее по существу является спекулятивным и, вероятно, связано с предоставлением компиляторам разумной свободы действий и облегчением их работы. Или это может быть какая-то историческая проблема. - person Nicol Bolas; 07.09.2012
comment
@RobertDailey: Или, может быть, составители спецификаций были достаточно мудры, чтобы понять, что одобрение такого кода, как a = a++ + ++a, — ужасная идея;) - person Nicol Bolas; 07.09.2012
comment
@NicolBolas ужасная идея, это твое личное мнение? Где факты? :) Я не могу придумать вариант использования, который сделал бы его ужасным, разрушительным или чем-то еще. Но, ужасно с точки зрения удобочитаемости и того факта, что это не имеет особого смысла, я согласен. - person void.pointer; 07.09.2012

Это, вероятно, слишком упрощенное объяснение, но я думаю, что это потому, что нет способа решить, когда код «сделан» с «а». Это делается после приращения или присваивания? Резолюция получается круговой. Присваивание после приращения изменяет семантику применения увеличенного значения. То есть код не выполняется с "а" до тех пор, пока "а" не увеличивается, но не увеличивается до тех пор, пока не будет выполнено присваивание. Это почти языковая версия тупика.

Как я уже сказал, я уверен, что это не самое лучшее «академическое» объяснение, но именно так я втиснул его между своими ушами. Надеюсь, это как-то полезно.

person David W    schedule 07.09.2012

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

В этом случае побочный эффект приращения не упорядочивается ни до, ни после присваивания, поэтому выражение имеет неопределенное поведение.

person Mike Seymour    schedule 07.09.2012

Дело здесь в том, что на некоторых архитектурах ЦП, таких как Intel Itanium, эти две операции могут быть распараллелены компилятором на уровне инструкций, но четкое определение вашей конструкции запрещает это. Во время спецификации точек следования эти архитектуры были в основном гипотетическими, и, поскольку Itanium потерпел неудачу, можно утверждать, что по состоянию на 2012 год большая часть этого является ненужной сложностью языка. По сути, у любой архитектуры, все еще широко используемой, нет возможных недостатков, и даже для Itanium преимущество в производительности было минимальным, а головная боль при написании компилятора, который мог бы даже использовать это преимущество, была огромной.

Также обратите внимание, что в C++11 точки следования были заменены последовательностью до и последовательностью после, что сделало больше ситуаций, подобных этой, четко определенными.

person Puppy    schedule 07.09.2012
comment
Даже на современных машинах возможная проблема заключается в том, что если a и b являются ссылками, идентифицирующими одни и те же int, а x и y являются переменными int, код, подобный a=b++; x=a; ...; y=b;, может оцениваться как temp=b; a=temp; b=temp+1; x=temp; ... y=a;, ведя себя так, как будто a спонтанно изменяется между присвоениями x. и y. Я хотел бы, чтобы Стандарт определил модель выполнения, которая признавала бы заразный недетерминизм, но оставалась бы на рельсах, поскольку предельные выгоды от разрешения произвольного поведения сверх этого незначительны, но было бы полезно ограничить... - person supercat; 21.05.2016
comment
... последствия перекрывающихся назначений в случаях, когда не имеет значения, какие значения вычисляются в определенных крайних случаях, при условии, что код остается на рельсах. Требование к программистам защищаться от таких назначений даже в этих случаях может сделать код менее эффективным, чем позволять им давать произвольные результаты. - person supercat; 21.05.2016

Этот оператор a=a++ имеет два результата и два присваивания:

a=a

(потому что это постинкремент) И

a=a+1

Эти назначения явно приводят к другому конечному значению для a.

Составители стандарта c не указали, какое из двух присваиваний должно быть записано в a первым, а какое вторым, поэтому авторы компилятора могут свободно выбирать то, что им нравится, в любой конкретной ситуации.

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

person Alex Brown    schedule 07.09.2012

Позвольте мне рассмотреть основную проблему в утверждении a = a++. Мы хотим достичь всех следующих вещей:

  • определить значение a (возвращаемое значение a++, #1)

  • приращение a (побочный эффект a++, #2)

  • присвоить старое значение a (эффект присваивания, #3)

Есть два возможных способа, которыми это можно было бы упорядочить:

  1. Сохраните исходный a в a (без операции); затем увеличьте a. То же, что и a = a; ++a;. Это последовательность #1-#3-#2.

  2. Оценить a, увеличить a, присвоить исходное значение обратно a. То же, что и b = a; ++a; a = b;. Это последовательность #1-#2-#3.

Поскольку предписанной последовательности нет, любая из этих операций допустима. Но у них разный конечный результат. Ни одна последовательность не является более естественной, чем другая.

person Kerrek SB    schedule 07.09.2012