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

Что такое "точки последовательности"?

Какова связь между неопределенным поведением и точками последовательности?

Я часто использую смешные и запутанные выражения, такие как a[++i] = i;, чтобы чувствовать себя лучше. Почему я должен прекратить использовать их?

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

(Примечание. Предполагается, что это будет вход в FAQ по C++ в Stack Overflow. Если вы хотите критиковать идею предоставления FAQ в этой форме, то публикация в meta, с которой все это началось, будет подходящим местом для этого. этот вопрос отслеживается в чате C++, где идея FAQ возникла в первую очередь, поэтому ваш ответ, скорее всего, будет прочитан теми, кто придумал эту идею.)

5 ответов

Решение

С ++98 и С ++03

Этот ответ для более старых версий стандарта C++. Версии стандарта C++11 и C++14 формально не содержат "точек последовательности"; вместо этого операции "секвенируются до", "не секвенированы" или "неопределенно секвенированы". Чистый эффект по сути тот же, но терминология другая.


Отказ от ответственности: хорошо. Этот ответ немного длинный. Так что наберитесь терпения, читая его. Если вы уже знаете эти вещи, их повторное чтение не сойдет с ума.

Пререквизиты: элементарные знания C++ Standard


Каковы Очки Последовательности?

Стандарт говорит

В определенных точках в последовательности выполнения, называемых точками последовательности, все побочные эффекты предыдущих оценок должны быть завершены, и никаких побочных эффектов последующих оценок не должно быть. (§1.9/7)

Побочные эффекты? Каковы побочные эффекты?

Оценка выражения производит что-то, и если, кроме того, происходит изменение в состоянии среды выполнения, говорят, что выражение (его оценка) имеет некоторый побочный эффект (ы).

Например:

int x = y++; //where y is also an int

В дополнение к операции инициализации значение y меняется из-за побочного эффекта ++ оператор.

Все идет нормально. Переходя к точкам последовательности. Определение чередования seq-точек, данное автором 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 (в LHS и в RHS) непосредственно участвуют в вычислении значения, которое будет записано. Так что все в порядке.

Это правило эффективно ограничивает юридические выражения теми, в которых доступы явно предшествуют модификации.

Пример 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 здесь.

Это продолжение моего предыдущего ответа и содержит материал, связанный с C++11.,


Пререквизиты: элементарные знания по связям (математика).


Правда ли, что в C++11 нет точек последовательности?

Да! Это очень верно.

Точки последовательности были заменены отношениями " Последовательный до" и " Последовательный после" (и " Последовательности и неопределенные последовательности") в C++11.


Что именно это за последовательность?

Sequenced Before (§1.9 / 13) - это отношение, которое:

между оценками, выполняемыми одним потоком и вызывает строгий частичный порядок 1

Формально это означает, что с учетом любых двух оценок (см. Ниже) A а также B, если A последовательность перед B затем исполнение A должно предшествовать исполнению B, Если A не секвенируется раньше B а также B не секвенируется раньше A, затем A а также B не секвенированы 2.

Оценки A а также B неопределенно упорядочены, когда либо A последовательность перед B или же B последовательность перед A, но это не указано, какой 3.

[ЗАМЕТКИ]
1: строгий частичный порядок является бинарным отношением "<" за набор P который asymmetric , а также transitive т.е. для всех a , b , а также c в P у нас есть что:
........(я). если a asymmetry );
........ (II). если a transitivity ).
2: выполнение неупорядоченных оценок может перекрываться.
3: Оценки с неопределенной последовательностью не могут перекрываться, но любая из них может быть выполнена первой.


Что означает слово "оценка" в контексте C++11?

В C++11 оценка выражения (или подвыражения) в целом включает в себя:

  • вычисления значений (включая определение идентичности объекта для оценки glvalue и выборку значения, ранее назначенного объекту для оценки prvalue) и

  • инициирование побочных эффектов.

Теперь (§1.9/14) говорит:

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

  • Тривиальный пример:

    int x;x = 10;++x;

    Расчет стоимости и побочный эффект, связанный с ++x секвенируется после вычисления значения и побочного эффекта x = 10;


Так что должна быть какая-то связь между неопределенным поведением и вышеупомянутыми вещами, верно?

Да! Правильно.

В (§1.9/15) было упомянуто, что

Если не указано иное, вычисления операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными 4.

Например:

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Оценка операндов + Операторы не секвенированы относительно друг друга.
  2. Оценка операндов << а также >> операторы не секвенированы относительно друг друга.

4: В выражении, которое оценивается более одного раза во время выполнения программы, непоследовательные и неопределенно последовательные оценки ее подвыражений не должны выполняться последовательно в различных оценках.

(§1.9/15) Вычисления значений операндов оператора секвенируются до вычисления значения результата оператора.

Это означает, что в x + y расчет стоимости x а также y секвенируются до вычисления значения (x + y),

Важнее

(§1.9/15) Если побочный эффект на скалярный объект не секвенирован относительно

(а) еще один побочный эффект на тот же скалярный объект

или же

(б) вычисление значения с использованием значения того же скалярного объекта.

поведение не определено.

Примеры:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

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

Выражения (5), (7) а также (8) не вызывайте неопределенное поведение. Проверьте следующие ответы для более подробного объяснения.


Заключительное примечание:

Если вы найдете какой-либо недостаток в сообщении, пожалуйста, оставьте комментарий. Опытные пользователи (с rep >20000), пожалуйста, не стесняйтесь редактировать пост для исправления опечаток и других ошибок.

C++17 (N4659) включает предложение " Уточнение порядка оценки выражений для Idiomatic C++", в котором определяется более строгий порядок оценки выражений.

В частности, было добавлено следующее предложение:

8.18 Операторы присваивания и составного присваивания:
....

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

Это делает допустимым несколько случаев ранее неопределенного поведения, включая рассматриваемый:

a[++i] = i;

Однако несколько других подобных случаев все еще приводят к неопределенному поведению.

В N4140:

i = i++ + 1; // the behavior is undefined

Но в N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Конечно, использование совместимого с C++17 компилятора не обязательно означает, что нужно начинать писать такие выражения.

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

f (a,b)

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

В C99(ISO/IEC 9899:TC3) который, по-видимому, отсутствует в этом обсуждении, до сих пор делаются следующие указания относительно порядка оценки.

[...] порядок вычисления подвыражений и порядок возникновения побочных эффектов не определены. (Раздел 6.5 с. 67)

Порядок оценки операндов не указан. Если предпринята попытка изменить результат оператора присваивания или получить к нему доступ после следующей точки последовательности, поведение [sic] не определено (Раздел 6.5.16, стр. 91).

Другие вопросы по тегам