Неопределенные точки поведения и последовательности - PullRequest
956 голосов
/ 14 ноября 2010

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

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

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

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

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

Ответы [ 5 ]

663 голосов
/ 14 ноября 2010

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 здесь .

272 голосов
/ 15 ноября 2010

Это продолжение моего предыдущего ответа и содержит материал, связанный с 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, имеем:
........ (i). если 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)Если побочный эффект на скалярный объект не секвенирован относительно

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

или

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

поведение undefined .

Примеры:

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)

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

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


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

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

24 голосов
/ 12 сентября 2017

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

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

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

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

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

a[++i] = i;

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

In 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 компилятора не обязательно означает, что нужно начинать писать такие выражения.

11 голосов
/ 07 декабря 2010

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

f (a,b)

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

0 голосов
/ 26 октября 2018

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

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

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

...