Неупорядоченные вычисления значений (также известные как точки последовательности)

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

Данный

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 имеет неопределенное поведение.

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


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


person Chubsdad    schedule 04.10.2010    source источник
comment
+ !: Хороший вопрос. Я буду следить за ответами.   -  person Arun    schedule 04.10.2010
comment
@Chubsdad: Я согласен с тем, что сказал @James McNellis в своем ответе (который он впоследствии удалил). Все 4 выражения вызывают UB в C ++ 0x [IMHO]. Думаю, вам стоит задать этот вопрос на csc ++ (comp.std.c ++). :)   -  person Prasoon Saurav    schedule 04.10.2010
comment
@Prasoon Saurav: Почему Expr3 имеет неопределенное поведение? Я думал, что все будет хорошо. gcc / comeau / llvm (demo) также компилируются без предупреждения.   -  person Chubsdad    schedule 04.10.2010
comment
Это потому, что побочные эффекты, связанные с ++ [внутренним] и ++ [внешним], не упорядочены относительно друг друга (хотя вычисления значений последовательны). :)   -  person Prasoon Saurav    schedule 04.10.2010
comment
Ознакомьтесь с этим. Было упомянуто, что Some more complicated cases are not diagnosed by -Wsequence-point option, and it may give an occasional false positive result,......   -  person Prasoon Saurav    schedule 04.10.2010
comment
@Prasoon, если вы говорите, что это неопределенное поведение, вам придется придумать формулировку C ++ 0x (в настоящее время n3126), которая поддерживает эту точку. Простое цитирование Джеймса Канце не докажет сути, приятель.   -  person Johannes Schaub - litb    schedule 05.10.2010
comment
И я, и Кай-Уве Букс показали, как определенность следует из различных правил C ++ 0x. Намерение может быть, а может и не совпадать с тем, что отражает формулировка, но это совсем другая история.   -  person Johannes Schaub - litb    schedule 05.10.2010
comment
Йоханнес Шауб - litb: Пока вы занимаетесь этим, сообщите нам, является ли это правильный способ визуализации этих выражений, или я пропускаю какой-либо случай с таким мышлением (с точки зрения вызова функции оператора для собственных типов, даже если они не существуют по практичности) кроме как в $ 13,6 / 18   -  person Chubsdad    schedule 05.10.2010
comment
Например, это мышление также объясняет ++ i = 0 (operator = (operator ++ (i), 0) как четко определенное поведение.   -  person Chubsdad    schedule 05.10.2010


Ответы (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-ридера. Я все равно ложился спать и вставал на последних двух правках… верно; v).

§5.17 / 1, определяющий операторы присваивания, говорит

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

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

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

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

  • Оцените 1 на дальнем правом углу и спуститесь в круг.
  • Оцените внутренний 1, а также значение (prvalue) и адрес (glvalue) x.
  • Now we need the value of the += subexpression.
    • We're done with the value computations for that subexpression.
    • Побочный эффект присваивания должен быть упорядочен до того, как станет доступно значение присваивания!
  • Присвойте новое значение x, которое идентично результату glvalue и prvalue подвыражения.
  • Мы вышли из леса. Все выражение теперь уменьшено до x +=1.

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

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

Редактировать # 4: (О, какую запутанную паутину мы плетем)

Йоханнес снова отмечает, что в i == ++i; значение gl (также известное как адрес) 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 имеет значение gl, отличное от (но идентичное) i. Это четко определено, а i = ++i нет? Это представляет собой тривиальное преобразование, которое компилятор может применить к любому случаю.

1.9 / 15 следует сказать

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

person Potatoswatter    schedule 04.10.2010
comment
Извините, я немного поздно упомянул C ++ 0X в своем посте - person Chubsdad; 04.10.2010
comment
@Potato AFAIK, это просто термин «точка последовательности», который устарел в пользу более четкой формулировки, но он все еще существует. - person NullUserException; 04.10.2010
comment
@NullUser: есть концепция последовательности, но способ C сказать, что машина находится либо в полностью определенном состоянии, либо в неопределенном, больше не существует. - person Potatoswatter; 04.10.2010
comment
Я тоже так думал. Но stackoverflow.com/questions/3850040/ внес некоторые изменения в мыслительный процесс. Путаница теперь в 'i = ++ i;' и я не смог правильно подумать об этом. В этом посте я пытаюсь проверить, верен ли мой мыслительный процесс (мышление в терминах эквивалента оператора). - person Chubsdad; 04.10.2010
comment
@Potatoswatter: все четыре выражения вызывают UB в C ++ 0x < / а> - person Prasoon Saurav; 04.10.2010
comment
@Chubsdad: значение ++i является операндом оператора =, поэтому, по крайней мере, согласно этому абзацу, эффекты упорядочены. Есть ли что-то еще, что делает его UB, я проверю ссылку Prasoon дальше. - person Potatoswatter; 04.10.2010
comment
@Potatoswatter: собственные выражения операторов не эквивалентны перегруженным выражениям операторов. Это стандартное? Вы говорите здесь о разнице в расположении точки последовательности при привязке значений к аргументам функции между встроенным / собственным / по умолчанию оператором = и перегруженным оператором =, определенным, как указано выше, в пользовательском коде? Можно ли привести пример случая нативного типа? Итак, если я определил перегруженный ++ и напишу i ++, это хорошо определенное поведение - иначе undefined? А это относится к C ++ 98/03 и C ++ 0x? - person Peter McG; 04.10.2010
comment
@Prasoon: Нет, в этих примерах без последовательности используется постинкремент. Йоханнес приводит пример преинкремента i = v[++i] и утверждает, что побочным эффектом является сохранение (i=i+1) неупорядоченного по отношению к следующему, явному назначению… это еще один аргумент, и, возможно, хороший. Но пока я слишком сонный, чтобы самостоятельно его оценивать. - person Potatoswatter; 04.10.2010
comment
@Peter: В начале предложения о перегруженных операторах и начале предложения о выражениях обсуждается разница в точках последовательности. Например, &&, || и , имеют совершенно разную последовательность. Перегруженные операторы - это вызовы функций, а операторы раздела 5 - нет. (Изменить: кроме оператора вызова функции. Я иду спать.) - person Potatoswatter; 04.10.2010
comment
@Potatoswatter: Я не очень уверен, но все же думаю, что ++ ++i и i = ++i оба являются UB в C ++ 0x. Прочтите сообщения Джеймса Канце [в конце обсуждения]. ;) - person Prasoon Saurav; 04.10.2010
comment
@Prasoon: Да, читать тирады Usenet - это рутинная работа, но §1.9 / 12 и 15 действительно довольно ясно об этом говорят. Обновленный ответ. - person Potatoswatter; 04.10.2010
comment
@Prasoon, обсуждение, которое вы связываете с рисунками, показывает, что выражение 3 не вызывает неопределенное поведение в C ++ 0x в соответствии с черновиком. Даже Джеймс Канце соглашается с этой очевидной вещью после того, как ему сказали об этом сто один раз (он сомневается, что assign - это побочный эффект ... но если это не модификация, что это?). Джеймс Канце явно не слушает того, что пишут. Обратите внимание, как я пишу в середине этого обсуждения, как упорядоченное до является транзитивным отношением. Он просто игнорирует это, и, наконец, когда кто-то упоминает это, он говорит: «Оооо, возможно, ты прав, капитан!». - person Johannes Schaub - litb; 05.10.2010
comment
@Johannes: Я думаю, вы имеете в виду неопределенность Expr1 ... в любом случае, я не понимаю, как вы понимаете, что побочный эффект первого ++ в Expr3 упорядочен иначе, чем = в Expr1. Две операции ++ генерируют два разных присваивания двум разным значениям, и 1.9 / 15 не упорядочивает их. - person Potatoswatter; 05.10.2010
comment
@Potatoswatter да, я имел в виду неопределенность Expr1, извините :) Надо идти работать. А пока посмотрите абзац для присвоения в 5.x, где все это упорядочено. До встречи :) - person Johannes Schaub - litb; 05.10.2010
comment
мы вернулись на круги своя. Почему у 1 четко определенное поведение? Это то, что оказалось неправильным в ходе дискуссии по Usenet, не так ли? - person Chubsdad; 05.10.2010
comment
@Chubdad: потому что ++i является подвыражением, эквивалентным выражению присваивания (5.3.2 / 1), побочный эффект которого упорядочивается перед вычислением значения выражения присваивания. (5.17 / 1) - person Potatoswatter; 05.10.2010
comment
Это то, что я чувствовал изначально. Но форум обсуждения usenet, похоже, пришел к выводу, что поведение expr1 неправильно сформировано. @litb также аргументирует это - person Chubsdad; 05.10.2010
comment
@Potatoswatter, как говорит @Chubsdad, 1 определенно неопределенное поведение. Да, побочный эффект ++i упорядочивается до вычисления значения ++i. Это упорядочивает побочный эффект приращения перед реальным побочным эффектом присваивания i. Но у нас также есть вычисление значения i в левой части в i = ++i, которое не упорядочено относительно вычисления значения правой стороны. Это то, что делает его неопределенным. Обратите внимание, что вычисление значения может означать не только чтение значения, но и вычисление того, к какому объекту относится lvalue для оценки glvalue. См. Ссылку на stackoverflow выше. - person Johannes Schaub - litb; 05.10.2010
comment
Ба, извини за отток, старый на… - person Potatoswatter; 05.10.2010
comment
@Potatoswatter Теперь я понимаю, о чем вы. - person Johannes Schaub - litb; 05.10.2010
comment
@Johannes: извините, у меня пропало соединение, и я ушел, забыв, что вы ждали ... опубликовал обновление. - person Potatoswatter; 05.10.2010
comment
@Potatoswatter Я удалил свой ответ, потому что вы подробно остановились на своем, чтобы дать разумное обоснование. - person Johannes Schaub - litb; 05.10.2010
comment
@Johannes: Думаю, это ваш звонок, я подумал, что это хороший ответ. Вы действительно думаете, что рабочий документ все еще находится в таком состоянии? Считаете ли вы, что DR был отправлен, и хотели бы вы это сделать? Алисдэр не ответил на несколько моих последних писем, поэтому я думаю, что я его рассердил; v). - person Potatoswatter; 05.10.2010
comment
@Potatoswatter: j = ++ i; также должно быть неопределенное поведение. j - это просто псевдоним для «i», не так ли? Я (заглавная буква I) не понимаю этого и продолжаю демонстрировать неопределенное поведение ... :) - person Chubsdad; 06.10.2010
comment
@Chubsdad: Несмотря на то, что это псевдоним, его оценка glvalue не требует оценки glvalue i. Вообще говоря, оценка ссылки не требует наличия исходного объекта. Нет причин, по которым это должен быть UB, поэтому имеет смысл иметь простую лазейку или преобразование в код, который не является UB. - person Potatoswatter; 06.10.2010
comment
его оценка glvalue не требует оценки i. Вообще говоря, для оценки ссылки не требуется, чтобы исходный объект был под рукой: я в этом очень сомневаюсь. У вас есть ссылка на эту «справку» :) - person Chubsdad; 06.10.2010
comment
@Chubsdad: Рассмотрим функцию f( int &i, int &j ) { j = ++ i; } … f( i, i ); Все еще так думаете? Ссылка является псевдонимом, потому что ее glvalue оценивается идентично, а не потому, что она синтаксически ссылается на исходный объект. - person Potatoswatter; 06.10.2010
comment
@Potatoswatter: Насколько я понимаю, «j» - это псевдоним для «i», и даже если «i» не входит в область видимости, «j» можно использовать до тех пор, пока «i» остается допустимым объектом, которым он был изначально. Я не уверен, понимаю ли я, что «glvalue оценивает одинаково» против «glvalue одинаково». - person Chubsdad; 06.10.2010
comment
Это относится к 5.5. Если выражение изначально имеет тип «ссылка на T» (8.3.2, 8.5.3), тип корректируется на T перед любым дальнейшим анализом. The expression designates the object or function denoted by the reference, а выражение - это lvalue или xvalue, в зависимости от выражения. - person Chubsdad; 06.10.2010
comment
@Chubsdad: он обозначает его идентичным lvalue; вот и все, и короче. Преобразование Lvalue-to-rvalue реализует ссылки, имеющие значение референтного объекта. Ссылка не сообщает компилятору, что нужно посмотреть на указанную переменную и получить ее lvalue, потому что он может не знать, на какую переменную ссылаются. Компилятор вычисляет lvalue ссылки, и это lvalue идентифицирует объект. Если вы хотите продолжить обсуждение этого вопроса, задайте новый вопрос. - person Potatoswatter; 06.10.2010
comment
@Potatoswatter: Ваше желание - моя команда :) (http://stackoverflow.com/questions/3870172/evaluation-of-a-reference-expression) - person Chubsdad; 06.10.2010
comment
Я понял. Но мои сомнения остаются. Возьмем i = ++ i. Согласно 13.6 / 18 его можно рассматривать как «оператор = (я, оператор ++ (я))». Побочный эффект для скалярного объекта («i» из-за 2-го аргумента) не упорядочен относительно вычисления значения того же скалярного объекта («i» для 1-го аргумента). Следовательно, поведение не должно быть определено. Можете ли вы сказать мне, почему это должно быть четко определено (как вы упомянули в своем сообщении) с этой точки зрения мышления? - person Chubsdad; 07.10.2010
comment
@Chubsdad: По стандарту он не определен, как объяснил Йоханнес. (Хотелось бы, чтобы он не удалил свой ответ.) Однако для компилятора практически невозможно произвести какое-либо поведение, кроме желаемого, потому что предположительно зависимое значение является константой. Опять же, пункт 13.6 не применяется. См. Отказ от ответственности в п. 13.6 / 1. Эти функции-кандидаты участвуют в процессе разрешения перегрузки оператора, как описано в п. 13.3.1.2, и не используются ни для каких других целей. Это просто фиктивные объявления; семантика вызова функций не существует по отношению к встроенным операторам. - person Potatoswatter; 07.10.2010
comment
@Potatoswatter: Итак, вы хотите изменить свой пост, поскольку в нем говорится, что Expr1 правильно сформирован? - person Chubsdad; 07.10.2010
comment
@Chubsdad: Нет, последнее изменение полностью посвящено обсуждению Expr1, поэтому проблема четко сформулирована. Я немного устал от этого. - person Potatoswatter; 07.10.2010
comment
@Chubsdad Я думаю, что @Potatoswatter имеет в виду то, что Std говорит, используя значение того же скалярного объекта. Значение объекта не используется, если вы просто используете lvalue, которое относится к объекту. Вы должны действительно прочитать значение, несмотря на странное вычисление значения термина. Но термин значение при вычислении значения, похоже, относится не к значению объекта, а к значению выражения. То есть результат выражения glvalue или prvalue. Но я действительно думаю, что Стандарт должен быть более ясным по этому вопросу. - person Johannes Schaub - litb; 08.10.2010
comment
@Johannes Schaub - litb: О. Теперь я лучше понимаю. Для меня это было немного загадочно. Я думаю, что начинаю понимать основную проблему, но еще не понял. - person Chubsdad; 09.10.2010
comment
Я нашел два DR, которые поддерживают этот ответ: open- std.org/jtc1/sc22/wg21/docs/cwg_defects.html#637 и open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#222 - person Johannes Schaub - litb; 10.10.2010
comment
@PrasoonSaurav All four expressions invoke UB in C++0x вы ошибаетесь, поскольку i = ++i отлично определен в c ++ 0x. stackoverflow.com/questions/17400137/ - person Kolyunya; 01.07.2013

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

person supercat    schedule 01.11.2011