﻿# V567\. Modification of variable is unsequenced relative to another operation on the same variable\. This may lead to undefined behavior\.

Анализатор обнаружил выражение, которое приводит к неопределённому поведению программы\. Переменная неоднократно используется между двумя точками следования, при этом её значение изменяется\. В результате невозможно предсказать результат работы такого выражения\. Рассмотрим понятия "неопределённое поведение" и "точка следования" более подробно\. 

[Неопределённое поведение](https://pvs-studio.ru/ru/blog/terms/0066/) \(англ\. undefined behavior\) — свойство некоторых языков программирования \(наиболее заметно в C и C\+\+\) в определённых ситуациях выдавать результат, зависящий от реализации компилятора\. Другими словами, спецификация не определяет поведение языка в любых возможных ситуациях, а говорит: "При условии А результат операции Б не определён"\. Допускать такую ситуацию в программе считается ошибкой, даже если на некотором компиляторе программа успешно выполняется, она не будет кроссплатформенной и может отказать на другой машине, в другой ОС и даже при других настройках компилятора\.

[Точка следования](https://pvs-studio.ru/ru/blog/terms/0065/) \(англ\. sequence point\) — в императивном программировании любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих ещё отсутствуют\.

Их часто упоминают, говоря о C и C\+\+, поскольку в этих языках особенно просто записать выражение, значение которого может зависеть от неопределённого порядка проявления побочных эффектов\. Добавление одной или нескольких точек следования задаёт порядок более жестко и является одним из методов достижения устойчивого \(т\.е\. корректного\) результата\.

В C\+\+11 вместо точек следования ввели понятия sequenced before/after, sequenced и unsequenced\. Многие выражения, приводящие к неопределённому поведению в C\+\+03, стали определены \(например, `i = ++i`\)\. Эти правила также дополнялись в C\+\+14 и C\+\+17\. Анализатор выдаёт срабатывание в зависимости от используемого стандарта\.

Примеры неопределённого поведения в зависимости от стандартов:

```cpp
i = ++i + 2;       // undefined behavior until C++11
i = i++ + 2;       // undefined behavior until C++17
f(i = -2, i = -2); // undefined behavior until C++17
f(++i, ++i);       // undefined behavior until C++17,
                   // unspecified after C++17
i = ++i + i++;     // undefined behavior
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
```

Точки следования необходимы в ситуации, когда одна и та же переменная изменяется в выражении более одного раза\. Часто в качестве примера приводят выражение `i=i++`, в котором происходит присваивание переменной `i` и её же инкремент\. Какое значение примет `i`? Стандарт языка должен либо указать одно из возможных поведений программы как единственно допустимое, либо указать диапазон допустимых поведений, либо указать, что поведение программы в данном случае совершенно не определено\. В языке C вычисление выражения `i = i++` приводит к неопределённому поведению, поскольку это выражение не содержит внутри себя ни одной точки следования\.

В C и C\+\+ определены следующие точки следования:

1. Между вычислением левого и правого операндов в операторах `&&` \(логическом И\), `||` \(логическом ИЛИ\) и операторах\-запятых\. Например, в выражении `*p++ != 0 && *q++ != 0` все побочные эффекты левого операнда `*p++ != 0` проявятся до начала каких\-либо действий в правом\.
1. Между вычислением первого, второго или третьего операндов в операторе условия\. В строке `a = (*p++) ? (*p++) : 0` точка находится после первого операнда `*p++`\. При вычислении второго выражения, переменная `p` уже увеличена на 1\.
1. В конце всего выражения\. Эта категория включает в себя инструкции\-выражения \(`a=b;`\), выражения в инструкциях `return`, управляющие выражения в круглых скобках инструкций ветвления `if` или `switch` и циклов `while` или `do-while` и все три выражения в круглых скобках цикла `for`\.
1. Перед входом в вызываемую функцию\. Порядок, в котором вычисляются аргументы, не определён, но эта точка следования гарантирует, что все её побочные эффекты проявятся на момент входа в функцию\. В выражении `f(i++) + g(j++) + h(k++)` каждая из трёх переменных: `i`, `j` и `k`, принимает новое значение перед входом в `f`, `g` и `h` соответственно\. Однако порядок вызова функций `f()`, `g()`, `h()` не определён, следовательно, не определён и порядок инкремента `i`, `j`, `k`\. Значения `j` и `k` в теле функции f оказываются неопределенными\. Следует отметить, что вызов функции нескольких аргументов `f(a,b,c)` не является случаем применения оператора\-запятой и не определяет порядок вычисления значений аргументов\.
1. При возврате из функции на момент, когда возвращаемое значение будет скопировано в вызывающий контекст \(Явно описана только в стандарте С\+\+, в отличие от С\)\.
1. В объявлении с инициализацией на момент завершения вычисления инициализирующего значения, например, на момент завершения вычисления `(1+i++) в int a = (1+i++);`\.
1. В C\+\+ перегруженные операторы выступают в роли функций, поэтому точкой следования является вызов перегруженного оператора\.

Рассмотрим пример кода, взятого из реального приложения:

```cpp
while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
         Powers_of_Two_Reversed[m_nCurrentBitIndex++ & 31]))
{}
return (m_nCurrentBitIndex - BitInitial - 1);
```

Компилятор может вычислить вначале как левый, так и правый аргумент оператора `&`\. Это значит, что переменная `m_nCurrentBitIndex` может быть уже увеличена на единицу при вычислении `m_pBitArray[m_nCurrentBitIndex >> 5]`\. А может быть ещё и не увеличена\.

Этот код может долго и исправно работать\. Однако следует учитывать, что гарантированно корректно он будет себя вести только при сборке определённой версией компилятора с неизменным набором параметров компиляции\. Корректный вариант кода:

```cpp
while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
         Powers_of_Two_Reversed[m_nCurrentBitIndex & 31]))
{ ++m_nCurrentBitIndex; }
return (m_nCurrentBitIndex - BitInitial);
```

Этот код более не содержит неоднозначностей\. Заодно исчезла магическая константа `-1`\.

Программисты часто считают, что неопределённое поведение может возникать только при использовании постинкремента, в то время как преинкремент безопасен\. Это не так\. Рассмотрим пример общения на эту тему\.

Вопрос:

Скачал ознакомительную версию вашей студии, прогнал свой проект и получил такое предупреждение: V567 Undefined behavior\. The 'i\_acc' variable is modified while being used twice between sequence points\.

Код:

```cpp
i_acc = (++i_acc) % N_acc;
```

Как мне кажется, здесь нет undefined behavior, так как переменная `i_acc` не участвует в выражении дважды\.

Ответ:

Для языка C и для языка C\+\+ до стандарта C\+\+11 неопределённое поведение здесь есть, хотя вероятность проявления ошибки весьма мала\. Оператор `=` не является точкой следования\. Это значит, что сначала компилятор может поместить значение переменной `i_acc` в регистр, затем увеличить значение в регистре, вычислить выражение и записать результат в переменную `i_acc`, после чего вновь записать в эту переменную регистр с увеличенным значением\. В результате мы получим код вида:

```cpp
REG = i_acc;
REG++;
i_acc = (REG) % N_acc;
i_acc = REG;
```

Компилятор имеет на это полное право\. Конечно, на практике, скорее всего, он сразу увеличит значение переменной и тогда всё посчитается так, как ожидает программист, но полагаться на это нельзя\.

Рассмотрим ещё одну ситуацию, связанную с вызовом функций\.

Порядок вычисления аргументов функции не определён\. Если аргументами является изменяющаяся переменная, то результат будет непредсказуем\. Это [неуточнённое поведение](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D1%83%D1%82%D0%BE%D1%87%D0%BD%D1%8F%D0%B5%D0%BC%D0%BE%D0%B5_%D0%BF%D0%BE%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5)\. Рассмотрим пример:

```cpp
int A = 0;
Foo(A = 2, A);
```

Функция `Foo` может быть вызвана как с аргументами `(2, 0)`, так и с аргументами `(2, 2)`\. Порядок вычисления аргументов функции зависит от компилятора и настроек оптимизации\.

**Дополнительные ресурсы**

1. Википедия\. [Неопределённое поведение\.](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D1%91%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D0%BE%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5) 
1. Википедия\. [Точка следования\.](https://ru.wikipedia.org/wiki/%D0%A2%D0%BE%D1%87%D0%BA%D0%B0_%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F) 
1. Елена Сагалаева\. [Точки следования \(sequence points\)\.](http://alenacpp.blogspot.com/2005/11/sequence-points.html)
1. Klaus Kreft & Angelika Langer\. [Sequence Points and Expression Evaluation in C\+\+\.](http://www.angelikalanger.com/Articles/VSJ/SequencePoints/SequencePoints.html)
1. Дискуссия на сайте bytes\.com\. [Sequence points](http://bytes.com/topic/c/answers/212093-sequence-points)\. 
1. Дискуссия на сайте StackOverflow\.com\. [Why is a \= \(a\+b\) \- \(b\=a\) a bad choice for swapping two integers?](https://stackoverflow.com/questions/20800684/why-is-a-ab-b-a-a-bad-choice-for-swapping-two-integers) 
1. cppreference\.com\. [Order of evaluation](https://en.cppreference.com/w/cpp/language/eval_order)