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


84

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

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

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

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.

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


Является

++++++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;
        }
};

13
+1 отличный вопрос, на который были даны отличные ответы. Я чувствую, что должен сказать, что это все еще ужасный код, который следует отремонтировать, чтобы сделать его более читабельным, но вы, вероятно, все равно это знаете :)
Филипп Поттер

4
@ В чем вопрос: кто сказал, что это то же самое? или кто сказал, что это не то же самое? Разве это не зависит от того, как вы их реализуете? (Примечание: я предполагаю, что это тип, sопределяемый пользователем!)
Наваз,

5
Я не вижу, чтобы какой-либо скалярный объект
изменялся

3
@Johannes: тогда речь идет о скалярном объекте. Что это такое? Интересно, почему я никогда не слышал об этом раньше. Может быть, потому что в учебниках / C ++ - faq это не упоминается или не акцентируется? Чем он отличается от объектов встроенного типа?
Nawaz

3
@Phillip: Очевидно, я не собираюсь писать такой код в реальной жизни; на самом деле, ни один здравомыслящий программист не станет его писать. Эти вопросы обычно разрабатываются для того, чтобы мы могли лучше понять весь бизнес неопределенного поведения и точек последовательности! :-)
Nawaz

Ответы:


48

Похоже на код

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

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

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

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

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


12

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 );

Возник вопрос, есть ++++++ i; четко определен в C ++ 03?
Промышленный антидепрессант

11

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

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


итак ... поэтому результат не указан, а не UB, поскольку порядок вычисления выражений не указан?
Филип Поттер,

@Philip: unspecified означает, что мы ожидаем, что компилятор определит поведение, тогда как undefined не накладывает таких обязательств. Я думаю, что здесь он не определен, чтобы у компиляторов было больше места для оптимизации.
Matthieu M.

@Noah: Я тоже опубликовал ответ. Пожалуйста, проверьте это и поделитесь своими мыслями. :-)
Nawaz

1
@Philip: результат - UB, из-за правила в 5/4: «Требования этого параграфа должны выполняться для каждого допустимого порядка подвыражений полного выражения; в противном случае поведение не определено.». Если бы все допустимые порядки имели точки последовательности между модификацией ++iи чтением iна правой стороне присвоения, то порядок не был бы определен. Поскольку один из допустимых порядков выполняет эти две вещи без промежуточной точки последовательности, поведение не определено.
Стив Джессоп,

1
@Philip: Он не просто определяет неопределенное поведение как неопределенное поведение. Опять же, если диапазон неопределенного поведения включает некоторые, которые не определены, то общее поведение не определено. Если диапазон неопределенного поведения определен во всех возможностях, тогда общее поведение не определено. Но вы правы по второму пункту, я имел в виду определяемый пользователем aи встроенный i.
Стив Джессоп,

8

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

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

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

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


6

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

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уже были оценены, а затем возвращаемый объект вызывает operator=функцию, передавая ему обновленное значение iв качестве аргумента. Обратите внимание, что между этими двумя вызовами есть точка последовательности . А синтаксис гарантирует отсутствие конкуренции между этими двумя вызовами, иoperator[]будет вызван первым, и последовательно ++iпереданный ему аргумент также будет сначала оценен.

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

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


1
@Nawaz k++и kкоторые не разделены точками последовательности. Оба они могут быть оценены перед тем Sunили Funиным. Язык требует только то, что Funвычисляется раньше Sun, а не то, что Funаргументы вычисляются перед Sunаргументами. Я как бы снова объясняю то же самое, не имея возможности дать ссылку, поэтому мы не собираемся двигаться дальше.
Филип Поттер,

1
@Nawaz: потому что нет ничего, что определяло бы точку последовательности, разделяющую их. Есть точки последовательности до и после выполнения Sun, но Funаргумент ++kможет быть вычислен до или после этого. Есть точки последовательности до и после выполнения Fun, но Sunаргумент kможет быть вычислен до или после этого. Следовательно, один из возможных случаев состоит в том, что оба kи ++kоцениваются до того, как либо Sunили Funоцениваются, и поэтому оба находятся перед точками последовательности вызова функции, и поэтому нет точки последовательности, разделяющей kи ++k.
Филип Поттер,

1
@Philip: Я повторяю: чем эта ситуация отличается eat(i++);drink(10);sleep(i);? ... даже сейчас, можно сказать, i++может быть проведена оценка до или после этого?
Nawaz

1
@Nawaz: как я могу выразиться яснее? В примере Fun / Sun нет точки последовательности между kи ++k. В примере есть / пить, есть в качестве точки последовательности между iи i++.
Филип Поттер,

3
@ Филипп: это вообще не имеет смысла. Между Fun () и Sun () существует точка последовательности, но между их аргументом точек последовательности не существует. Это как сказать, между eat()и sleep()существует точка (точки) последовательности, но между ними нет ни одного аргумента. Как могут аргументы двух вызовов функций, разделенных точками последовательности, принадлежать одним и тем же точкам последовательности?
Nawaz
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.