пример - точка c++




Неопределенное поведение и точки последовательности (4)

C ++ 98 и C ++ 03

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

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

Предварительные требования : элементарное знание стандарта C ++

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

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

В определенных определенных точках последовательности выполнения, называемых точками последовательности , все побочные эффекты предыдущих оценок должны быть полными и никаких побочных эффектов последующих оценок не должно быть. (§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.

Другой пример here .

Теперь в Стандарте в §5/4 говорится

  • 1) Между предыдущей и следующей точкой последовательности скалярный объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения.

Что это значит?

Неофициально это означает, что между двумя точками последовательности переменная не должна изменяться более одного раза. В выражении выражения next sequence point обычно находится в конечной previous sequence point запятой, а previous sequence point находится в конце предыдущего оператора. Выражение также может содержать sequence points промежуточной sequence points .

Из приведенного выше предложения следующие выражения ссылаются на Undefined Behavior:

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 here .

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

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

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

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

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


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

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

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


C ++ 17 ( N4659 ) включает предложение Refining Expression Evaluation Order для 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, не обязательно означает, что следует начинать писать такие выражения.


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

Предварительные требования : элементарное знание отношений (математика).

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

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

Точки последовательности были заменены последовательностями Sequenced Before and Sequenced AfterUnsequenced and Indeterminately Sequenced ) в C ++ 11.

Что именно происходит в этой ситуации?

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

между оценками, выполненными одним thread и вызывает строгий частичный порядок 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 <b, то ¬ (b <a) ( asymmetry );
........ (II). если a <b и b <c, то a <c ( transitivity ).
2: Выполнение неощутимых оценок может перекрываться .
3: Неопределенно упорядоченные оценки не могут пересекаться , но либо могут быть выполнены в первую очередь.

В чем смысл слова «оценка» в контексте C ++ 11?

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

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

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

Теперь (§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) вычисление значения с использованием значения одного и того же скалярного объекта.

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

Примеры:

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)

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

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

Конечное примечание :

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





sequence-points