C ++ 98 и C ++ 03
Этот ответ предназначен для более старых версий стандарта C ++. Версии стандарта C ++ 11 и C ++ 14 формально не содержат «точек последовательности»; вместо этого операции «секвенируются до», «не секвенированы» или «неопределенно секвенированы». Чистый эффект, по сути, тот же, но терминология другая.
Отказ от ответственности : Хорошо. Этот ответ немного длинный. Так что наберитесь терпения, читая его. Если вы уже знаете эти вещи, их повторное чтение не сойдет с ума.
Предпосылки : элементарные знания C ++ Standard
Что такое очки последовательности?
Стандарт гласит
В определенных точках последовательности выполнения, называемых точками последовательности , все побочные эффекты предыдущих оценок
должны быть завершены, и никаких побочных эффектов последующих оценок не должно иметь место. (§1.9 / 7)
Побочные эффекты? Каковы побочные эффекты?
Оценка выражения производит что-то, и если, кроме того, происходит изменение в состоянии среды выполнения, говорят, что выражение (его оценка) имеет некоторые побочные эффекты.
Например:
int x = y++; //where y is also an int
В дополнение к операции инициализации изменяется значение y
из-за побочного эффекта оператора ++
.
Пока все хорошо. Переходя к точкам последовательности. Определение чередования последовательностей, приведенное автором comp.lang.c Steve Summit
:
Точка последовательности - это момент времени, когда пыль осела, и все побочные эффекты, которые были замечены до сих пор, гарантированно будут завершены.
Какие общие точки последовательности перечислены в Стандарте C ++?
Это:
в конце оценки полного выражения (§1.9/16
) (Полное выражение - это выражение, которое не является подвыражением другого выражения.) 1
Пример:
int a = 5; // ; is a sequence point here
при оценке каждого из следующих выражений после вычисления первого выражения (§1.9/18
) 2
a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)
(здесь a, b - оператор запятой; в func(a,a++)
,
- не оператор запятой, это просто разделитель между аргументами a
и a++
. Таким образом, поведение не определено в этот случай (если a
считается типом примитива)
при вызове функции (независимо от того, является ли функция встроенной), после оценки всех аргументов функции (если есть), которые
происходит перед выполнением любых выражений или операторов в теле функции (§1.9/17
).
1: Примечание: оценка полного выражения может включать в себя оценку подвыражений, которые не являются лексическими
часть полного выражения. Например, подвыражения, участвующие в оценке выражений аргумента по умолчанию (8.3.6), считаются созданными в выражении, которое вызывает функцию, а не в выражении, определяющем аргумент по умолчанию
2: Указанные операторы являются встроенными операторами, как описано в разделе 5. Когда один из этих операторов перегружен (раздел 13) в допустимом контексте, обозначая, таким образом, определяемую пользователем функцию оператора, выражение обозначает вызов функции, а операнды образуют список аргументов, без подразумеваемой точки последовательности между ними.
Что такое неопределенное поведение?
Стандарт определяет неопределенное поведение в разделе §1.3.12
как
поведение, которое может возникнуть при использовании ошибочной программной конструкции или ошибочных данных, к которым настоящий международный стандарт не предъявляет никаких требований 3 .
Неопределенное поведение также может ожидаться, когда этоМеждународный стандарт опускает описание любого явного определения поведения.
3: допустимое неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время перевода или выполнения программы документированным образом, характерным для среды (с или без
выдача диагностического сообщения), чтобы прекратить перевод или выполнение (с выдачей диагностического сообщения).
Короче говоря, неопределенное поведение означает что угодно может произойти от демонов, вылетающих из вашего носа, до того, что ваша девушка забеременеет.
Какова связь между неопределенным поведением и точками последовательности?
Прежде чем я начну это понимать, вы должны знать разницу между Неопределенное поведение, Неуказанное поведение и Поведение, определяемое реализацией .
Вы также должны знать, что the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified
.
Например:
int x = 5, y = 6;
int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.
Другой пример здесь .
Теперь Стандарт в §5/4
говорит
- 1) Между предыдущей и следующей точкой последовательности скалярному объекту должно быть сохранено его значение, измененное не более одного раза путем вычисления выражения.
Что это значит?
Неформально это означает, что между двумя точками последовательности переменная не должна изменяться более одного раза.
В выражении выражения next sequence point
обычно находится в конце точки с запятой, а previous sequence point
- в конце предыдущего выражения. Выражение также может содержать промежуточное значение sequence points
.
Из приведенного выше предложения следующие выражения вызывают неопределенное поведение:
i++ * ++i; // UB, i is modified more than once btw two SPs
i = ++i; // UB, same as above
++i = 2; // UB, same as above
i = ++i + 1; // UB, same as above
++++++i; // UB, parsed as (++(++(++i)))
i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)
Но следующие выражения хороши:
i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i); // well defined
int j = i;
j = (++i, i++, j*i); // well defined
- 2) Кроме того, предварительное значение должно быть доступно только для определения значения, которое будет сохранено.
Что это значит? Это означает, что если объект записан в пределах полного выражения, любой и все обращения к нему в пределах одного и того же выражения должны быть непосредственно вовлечены в вычисление значения, которое будет записано .
Например, в i = i + 1
весь доступ к i
(в L.H.S и в R.H.S) непосредственно задействован в вычислении записываемого значения. Так что все в порядке.
Это правило эффективно ограничивает юридические выражения теми, в которых доступы явно предшествуют модификации.
Пример 1:
std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2
Пример 2:
a[i] = i++ // or a[++i] = i or a[i++] = ++i etc
запрещено, потому что один из обращений к i
(тот, что в a[i]
) не имеет никакого отношения к значению, которое в итоге сохраняется в i (что происходит в i++
), и поэтому нет хороший способ определить - для нашего понимания или для компилятора - должен ли доступ осуществляться до или после сохранения увеличенного значения. Так что поведение не определено.
Пример 3:
int x = i + i++ ;// Similar to above
Последующий ответ для C ++ 11 здесь .