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

Считайте эту тему продолжением следующей темы:

Предыдущий выпуск
Неопределенное поведение и точки последовательности

Давайте вернемся к этому забавному и запутанному выражению (выделенные курсивом фразы взяты из темы выше * улыбка *):

i += ++i;

Мы говорим, что это вызывает неопределенное поведение. Я предполагаю, что, говоря это, мы неявно предполагаем, что type of i является одним из встроенных типов.

Что, если тип для i является определяемым пользователем типом? Скажем, его тип Index, который будет определен позже в этом посте (см. Ниже). Будет ли он по-прежнему вызывать неопределенное поведение?

Если да, то почему? Разве это не эквивалентно написанию i.operator+=(i.operator++()); или даже синтаксически проще i.add(i.inc());? Или они тоже вызывают неопределенное поведение?

Если нет, то почему? В конце концов, объект i изменяется дважды между последовательными точками последовательности. Вспомните практическое правило: выражение может изменять значение объекта только один раз между последовательными "точками последовательности". И если i += ++i является выражением, тогда оно должно вызывать неопределенное поведение. Если это так, то его эквиваленты i.operator+=(i.operator++()); и i.add(i.inc()); также должны вызывать неопределенное поведение, которое кажется неверным ! (насколько я понимаю)

Или i += ++i - это не выражение для начала? Если да, то что это такое и каково определение выражения?

Если это выражение и в то же время его поведение также четко определено, то это означает, что количество точек последовательности, связанных с выражением, так или иначе зависит от типа операндов, участвующих в выражении. Я прав (хотя бы частично)?


Кстати, а как насчет этого выражения?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

Вы также должны учитывать это в своем ответе (если вы точно знаете его поведение). :-)


Is

++++++i;

четко определен в C ++ 03? Ведь вот это,

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};

person Nawaz    schedule 09.01.2011    source источник
comment
+1 отличный вопрос, на который были даны отличные ответы. Я чувствую, что должен сказать, что это все еще ужасный код, который нужно отремонтировать, чтобы он был более читабельным, но вы, вероятно, все равно это знаете :)   -  person Philip Potter    schedule 09.01.2011
comment
с каких это пор s ++ совпадает с ++ s?   -  person    schedule 09.01.2011
comment
@ В чем вопрос: кто сказал, что это то же самое? или кто сказал, что это не то же самое? Разве это не зависит от того, как вы их реализуете? (Примечание: я предполагаю, что тип s определяется пользователем!)   -  person Nawaz    schedule 09.01.2011
comment
Я не вижу, чтобы какой-либо объект скаляр изменялся дважды между двумя точками последовательности ...   -  person Johannes Schaub - litb    schedule 09.01.2011
comment
@Nawaz: да ... как я и думал ...   -  person    schedule 09.01.2011
comment
@Johannes: тогда речь идет о скалярном объекте. Что это? Интересно, почему я никогда не слышал об этом раньше. Может быть, потому что в учебниках / C ++ - faq это не упоминается или не акцентируется? Отличается ли он от объектов типа встроенный?   -  person Nawaz    schedule 09.01.2011
comment
@Phillip: Очевидно, я не собираюсь писать такой код в реальной жизни; на самом деле, ни один здравомыслящий программист его не напишет. Эти вопросы обычно разрабатываются для того, чтобы мы могли лучше понять весь бизнес неопределенного поведения и точек последовательности! :-)   -  person Nawaz    schedule 09.01.2011
comment
@Nawaz, моя запись в часто задаваемых вопросах охватывает это. Он действительно говорит, что это применимо к скалярным объектам, потому что другие объекты либо немодифицируемы (массивы), либо просто не применимы к этому правилу (объекты классов).   -  person Johannes Schaub - litb    schedule 13.05.2011
comment
@Johannes: Да, я видел это сегодня утром. :-)   -  person Nawaz    schedule 13.05.2011
comment
В C ++ 11 i += ++i становится хорошо определенным даже для встроенных типов. stackoverflow.com/q/10655290/365496   -  person bames53    schedule 18.05.2012
comment
@Nawaz: очень хороший вопрос. никогда не задумывался о типах, определяемых пользователем.   -  person Destructor    schedule 28.08.2015


Ответы (5)


Похоже на код

i.operator+=(i.operator ++());

Прекрасно работает в отношении точек последовательности. В разделе 1.9.17 стандарта C ++ ISO говорится о точках последовательности и оценке функций:

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

Это может указывать, например, на то, что i.operator ++() в качестве параметра для operator += имеет точку последовательности после его оценки. Короче говоря, поскольку перегруженные операторы являются функциями, применяются обычные правила последовательности.

Кстати, отличный вопрос! Мне очень нравится, как вы заставляете меня понимать все нюансы языка, который я уже думал, что знаю (и думал, что думал, что знаю). :-)

person templatetypedef    schedule 09.01.2011

http://www.eelis.net/C++/analogliterals.xhtml Аналоговые литералы приходят в по моему мнению

  unsigned int c = ( o-----o
                     |     !
                     !     !
                     !     !
                     o-----o ).area;

  assert( c == (I-----I) * (I-------I) );

  assert( ( o-----o
            |     !
            !     !
            !     !
            !     !
            o-----o ).area == ( o---------o
                                |         !
                                !         !
                                o---------o ).area );
person Industrial-antidepressant    schedule 09.01.2011
comment
Возник вопрос, есть ++++++ i; четко определен в C ++ 03? - person Industrial-antidepressant; 09.01.2011

Как уже говорили другие, ваш пример i += ++i работает с определяемым пользователем типом, поскольку вы вызываете функции, а функции содержат точки последовательности.

С другой стороны, a[++i] = i не так повезло, если предположить, что a - это ваш базовый тип массива или даже определенный пользователем. Проблема в том, что мы не знаем, какая часть выражения, содержащего i, оценивается первой. Может случиться так, что ++i оценивается, передается operator[] (или необработанной версии), чтобы получить там объект, а затем значение i передается этому (что происходит после того, как i был увеличен). С другой стороны, возможно, сначала оценивается последняя сторона, сохраняется для последующего присвоения, а затем оценивается часть ++i.

person Edward Strange    schedule 09.01.2011
comment
итак ... поэтому результат не указан, а не UB, поскольку порядок вычисления выражений не указан? - person Philip Potter; 09.01.2011
comment
@Philip: unspecified означает, что мы ожидаем, что компилятор определит поведение, тогда как undefined не налагает таких обязательств. Я думаю, что здесь он не определен, чтобы дать компиляторам больше места для оптимизации. - person Matthieu M.; 09.01.2011
comment
@Noah: Я тоже опубликовал ответ. Пожалуйста, проверьте это и поделитесь своими мыслями. :-) - person Nawaz; 09.01.2011
comment
@Matthieu: нет, определяется реализацией требует, чтобы компилятор указывал поведение. unspecified требует, чтобы компилятор что-то выбрал, но не должен это документировать. он может каждый раз быть другим, если захочет. - person Philip Potter; 09.01.2011
comment
@Philip: результат - UB, из-за правила в 5/4: требования этого параграфа должны выполняться для каждого допустимого порядка подвыражений полного выражения; в противном случае поведение не определено. Если бы все допустимые упорядочения имели точки последовательности между модификацией ++i и чтением i на правой стороне присвоения, то порядок не был бы определен. Поскольку один из допустимых порядков выполняет эти две вещи без промежуточной точки последовательности, поведение не определено. - person Steve Jessop; 12.01.2011
comment
... вы можете смотреть на это как на совершенно разумную вещь, если не указано, определено ли поведение или не определено, тогда поведение не определено. Потому что, как вы говорите, неопределенное поведение может каждый раз отличаться от возможностей, установленных стандартом. И возможности, изложенные в стандарте, включали некоторые упорядочения с определенным поведением и некоторые упорядочения с неопределенным поведением. Так что компилятору как бы разрешено всегда выбирать UB. Ной просто описывает пару вероятных деталей реализации, а не все юридические возможности. - person Steve Jessop; 12.01.2011
comment
@Steve: во-первых, я думаю, что это просто определяет неопределенное поведение как неопределенное поведение, а это не так. В UB может произойти все, в то время как при неопределенном поведении может произойти что угодно из ограниченного набора. во-вторых, это все еще UB для определяемого пользователем i? Вызов функций, представленный operator++, заставляет точки последовательности между чтением и записью, независимо от того, оценивается ли LHS i++ до или после RHS i. - person Philip Potter; 12.01.2011
comment
@Philip: Он не просто определяет неопределенное поведение как неопределенное поведение. Опять же, если диапазон неопределенного поведения включает некоторые, которые не определены, тогда общее поведение не определено. Если диапазон неопределенного поведения определен во всех возможностях, тогда общее поведение не определено. Но вы правы во втором пункте, я имел в виду определяемый пользователем a и встроенный i. - person Steve Jessop; 12.01.2011
comment
@Steve Jessop: Было бы справедливо думать, что такое выражение, как foo ++, говорит, что как только другому коду больше не разрешается предполагать, что 'foo' не изменилось, прикрепите демона к ячейкам памяти 'foo, которые сбегут и сеют хаос, если ничего не подозревают код пытается получить к ним доступ; непосредственно перед тем, как другому коду будет разрешен доступ к foo, удалить демона, чтобы загрузить ячейки памяти с правильным значением? - person supercat; 22.03.2011
comment
@supercat: звучит примерно правильно, если ваш foo++ является подвыражением полного выражения. Затем, как только другой код больше не разрешен, это предыдущая точка последовательности, а непосредственно перед тем, как другой код будет разрешен, будет следующая точка последовательности. Вы, вероятно, можете думать о foo как о одержимом в это время, и это подвыражение является единственной частью выражения, имеющей к нему доступ. - person Steve Jessop; 22.03.2011
comment
@ Стив Джессоп: Верно. Моя точка зрения, которую многие люди не понимают, заключается в том, что не только нет гарантии, была ли переменная записана, но и нет гарантии, что попытка ее чтения или записи не приведет к очень плохим вещам. На самом деле, некоторые архитектуры кэширования включают в себя что-то вроде проверки областей разделяемой памяти и их последующей проверки обратно. Если оборудование накладывает блокировку, попытка доступа к объектам в неправильное время может привести к тупиковой ситуации. - person supercat; 22.03.2011
comment
Я понимаю, почему a[++i] = i не в порядке. Как насчет a[(++i)] = i? - person gsamaras; 01.07.2015
comment
Тип a не имеет значения, является ли это UB или нет - важен тип i. Если i является встроенным типом, то это UB, а если i - тип, определяемый пользователем, его поведение не определено. - person Chris Dodd; 22.10.2017

Я думаю, это четко определено:

Из проекта стандарта C ++ (n1905) §1.9 / 16:

«Также существует точка последовательности после копирования возвращаемого значения и перед выполнением любых выражений за пределами функции13). Некоторые контексты в C ++ вызывают оценку вызова функции, даже если соответствующий синтаксис вызова функции не появляется в блоке преобразования. [Пример: оценка нового выражения вызывает одну или несколько функций выделения и конструктора; см. 5.3.4. Другой пример: вызов функции преобразования (12.3.2) может возникать в контекстах, в которых нет Появляется синтаксис вызова функции. - конец примера] Точки последовательности при входе в функцию и выходе из функции (как описано выше) являются характеристиками вызовов функций, как они оцениваются, независимо от синтаксиса выражения , который вызывает функцию, может быть. "

Обратите внимание на часть, которую я выделил жирным шрифтом. Это означает, что действительно есть точка последовательности после вызова функции приращения (i.operator ++()), но перед вызовом составного присваивания (i.operator+=).

person Matthew Flaschen    schedule 09.01.2011

Хорошо. Прочитав предыдущие ответы, я снова подумал о своем вопросе, особенно о той части, которую только Ной пытался answer, но я не полностью убежден в нем.

a[++i] = i;

Дело 1:

Если a - массив встроенного типа. Тогда то, что сказал Ной, верно. Это,

a [++ i] = i не так повезло, если предположить, что a - это ваш базовый тип массива, или даже определенный пользователем. Проблема в том, что мы не знаем, какая часть выражения, содержащего i, вычисляется первой.

Итак, a[++i]=i вызывает неопределенное поведение, или результат не указан. Как бы то ни было, это не совсем четкое определение!

PS: В приведенной выше цитате зачеркнутый, конечно же, мой.

Случай 2:

Если a является объектом определяемого пользователем типа, который перегружает operator[], то опять же есть два случая.

  1. Если тип возврата перегруженной функции operator[] является встроенным, тогда снова a[++i]=i вызывает неопределенное поведение или результат не указан.
  2. Но если тип возврата перегруженной функции operator[] является определяемым пользователем типом, то поведение a[++i] = i четко определено (насколько я понимаю), поскольку в этом случае a[++i]=i эквивалентно записи a.operator[](++i).operator=(i);, которая совпадает с a[++i].operator=(i);. То есть присваивание operator= вызывается для возвращенного объекта a[++i], который кажется очень четко определенным, поскольку к моменту возврата a[++i] ++i уже были оценены, а затем вернул < / em> объект вызывает operator= функцию, передавая ей обновленное значение i в качестве аргумента. Обратите внимание, что между этими двумя вызовами есть точка последовательности. И синтаксис гарантирует, что между этими двумя вызовами нет конкуренции, и operator[] будет вызываться первым, и, последовательно, аргумент ++i, переданный ему, также будет сначала оценен.

Думайте об этом как о someInstance.Fun(++k).Gun(10).Sun(k).Tun();, в котором каждый последующий вызов функции возвращает объект определенного пользователем типа. Мне эта ситуация кажется больше такой: eat(++k);drink(10);sleep(k), потому что в обеих ситуациях существует точка последовательности после каждого вызова функции.

Пожалуйста, поправьте меня, если я ошибаюсь. :-)

person Nawaz    schedule 09.01.2011
comment
вы упустили суть. Проблема в том, что не указано, будет ли сначала вычисляться выражение i или выражение ++i; являются ли я или а пользовательским типом или нет. - person Philip Potter; 09.01.2011
comment
@Philip: Я добавил несколько пояснений к своему посту. Пожалуйста, прочтите последние несколько строк! - person Nawaz; 09.01.2011
comment
@Nawaz: в someInstance.Fun(++k).Gun(10).Sun(k).Tun(); возможно, что k в Sun(k) оценивается перед ++k в Fun(++k). Как только это будет сделано, компилятору нужно будет где-нибудь сохранить результат, потому что Fun() должен быть оценен до Sun(), потому что Sun() зависит от вывода Fun(). Эта зависимость не распространяется на аргументы Sun() и Fun(), в чем и заключается ваша проблема. - person Philip Potter; 09.01.2011
comment
@ Филип Поттер: возможно, что k в Sun (k) оценивается до ++ k в Fun (++ k) ... Почему так? Любая ссылка из языковой спецификации? - person Nawaz; 09.01.2011
comment
@Nawaz, к сожалению, у меня нет копии стандарта C ++. Но этот вопрос охватывает аналогичные проблемы. - person Philip Potter; 09.01.2011
comment
@Philip: эта проблема отличается от этой. в том, что два выражения i++ и i++ не разделены точками последовательности. i изменяется дважды между последовательными точками последовательности; но здесь, прежде всего, есть точка последовательности после каждого вызова функции; во-вторых, поскольку выражение ++i появляется в первом вызове функции, а после него есть точка последовательности, это означает, что между выражениями ++i и i также есть точки последовательности. - person Nawaz; 09.01.2011
comment
@Nawaz k++ и k не разделены точками последовательности. Оба они могут быть оценены перед оценкой Sun или Fun. Язык only требует, чтобы Fun оценивался перед Sun, а не чтобы аргументы Fun оценивались перед аргументами Sun. Я как бы снова объясняю то же самое, не имея возможности дать ссылку, поэтому мы не собираемся двигаться дальше. - person Philip Potter; 09.01.2011
comment
@Philip: k ++ и k не разделены точками последовательности ... как? только потому, что их ; нет? - person Nawaz; 09.01.2011
comment
@Philip: чем эта ситуация отличается от eat(i++);drink(10);sleep(i);? - person Nawaz; 09.01.2011
comment
@Nawaz: потому что нет ничего, что определяло бы точку последовательности, разделяющую их. Есть точки последовательности до и после выполнения Sun, но аргумент Fun ++k может быть вычислен до или после этого. Есть точки последовательности до и после выполнения Fun, но аргумент Sun k может быть вычислен до или после этого. Следовательно, одним из возможных случаев является то, что и k, и ++k оцениваются до того, как оцениваются либо Sun, либо Fun, и поэтому оба они находятся перед точками последовательности вызова функции, и поэтому нет точки последовательности, разделяющей k и ++k. - person Philip Potter; 09.01.2011
comment
@Philip: Повторяю: чем эта ситуация отличается от eat(i++);drink(10);sleep(i);? ... даже сейчас вы можете сказать, что i++ могут быть оценены до или после этого? - person Nawaz; 09.01.2011
comment
@Nawaz, потому что в этой ситуации ; является точной точкой последовательности между i++ и i. ›_‹ Мне нужна доска и личный кабинет, чтобы прояснить это. - person Philip Potter; 09.01.2011
comment
@ Филипп: какое это имеет значение? пока существует точка последовательности между этими двумя выражениями, мне это кажется нормальным. Не вижу разницы! - person Nawaz; 09.01.2011
comment
@Nawaz: как я могу выразиться яснее? В примере Fun / Sun между k и ++k нет точки последовательности. В примере есть / пить есть как точка последовательности между i и i++. - person Philip Potter; 09.01.2011
comment
@ Филипп: это вообще не имеет смысла. Между Fun () и Sun () существует точка последовательности, но между их аргументом точек последовательности не существует. Это как сказать, между eat() и sleep() существует точка (точки) последовательности, но между ними нет ни одного аргумента. Как могут аргументы двух вызовов функций, разделенных точками последовательности, принадлежать одним и тем же точкам последовательности? - person Nawaz; 09.01.2011
comment
@Nawaz: Я явно не могу объяснить вам это за это короткое время. возможно, вы сможете задать вопрос всей ТАКОЙ толпе. - person Philip Potter; 09.01.2011
comment
@Philip: Присоединяйтесь к этой теме здесь: stackoverflow.com/ questions / 4709727 / is-this-code-well-defined - person Nawaz; 17.01.2011
comment
@Nawaz: для полноты вы можете также рассмотреть случай массива определяемого пользователем типа. Думаю, это то же самое, что и ваш случай 2.1. - person Andriy Tylychko; 24.09.2011