V2667. MISRA. The value of an expression and its persistent side effects must be the same under all permitted evaluation orders and must be independent from thread interleaving.
Диагностическое правило основано на руководстве MISRA (Motor Industry Software Reliability Association) по разработке программного обеспечения.
Правило актуально только для языка C и языка C++ до стандарта С++11.
Анализатор обнаружил неупорядоченные относительно друг друга операции чтения и модификации одного и того же объекта. Поведение таких операций не определено, и они могут приводить к неожиданным результатам программы.
Стандарты C и C++ оставляют много свободы для компилятора в порядке вычисления выражений в целях оптимизации кода. Как следствие, на программиста накладывается ответственность за определение корректного написания порядка операций над объектом.
Для предотвращения неожиданных для программиста результатов или неопределённого поведения между двумя соседними точками следования (sequence points) стандартом MISRA запрещается:
- Модифицировать объект более одного раза.
- И модифицировать, и читать объект, если только такое чтение не способствует вычислению значения этого объекта.
- Модифицировать более одного
volatileили атомарного объекта. - Читать более одного
volatile-объекта или один и тот жеvolatile-объект больше одного раза. - Читать более одного раза один и тот же атомарный объект.
Примечание N1. Под volatile-объектом подразумевается переменная, объявленная с квалификатором volatile или поле структуры с таким квалификатором.
Пример:
volatile int a;
const volatile char b = 5;
volatile int* pointer;
_Atomic volatile int av;
struct TestStruct
{
int x;
};
volatile TestStruct ts;
В этом примере и ts, и ts.x — volatile-объекты.
Под атомарным объектом подразумевается переменная, объявленная со спецификатором _Atomic(....) или квалификатором _Atomic, или поле структуры с таким спецификатором или квалификатором.
Пример:
_Atomic int a;
_Atomic const float f = 1.0;
_Atomic(char) c;
atomic_long l;
_Atomic volatile int av;
struct TestStruct
{
int x;
};
_Atomic TestStruct ts;
В этом примере и ts, и ts.x — атомарные-объекты.
Примечание N2. Точка следования в императивном программировании — любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих ещё отсутствуют. Для языка C точки следования описаны в Annex C стандарта.
Примечание N3. Все части выражения рассматриваются для определения чтения/записи независимо от любых известных значений. Например:
int n = 1;
int m = n || n++ + n;
Несмотря на то, что выражение n++ + n в таком контексте никогда не выполнится, анализатор всё равно выдаст предупреждение.
Рассмотрим примеры для каждого из пунктов правила.
Пример N1. Запрещается модифицировать объект более одного раза между соседними точками следования:
int n, m;
n = n++;
n = ++n + n;
n = (n = 4);
m = n++ + ++n;
foo(n++, n++);
В каждой строке n модифицируется дважды, что нарушает пункт N1.
Пример корректного кода:
n = m++;
n = m + m * 200;
n = 4 + (m = 2);
n = m = n + m;
foo(n++, m++);
Пример N2. Запрещается и модифицировать, и читать объект, если только такое чтение не способствует вычислению значения этого объекта:
int n, m;
int m = n + (n = 2);
m = n++ + n;
m = foo(n, n++);
В каждой строке n модифицируется и читается, что нарушает пункт N2.
Пример корректного кода:
n = n + 2;
n++;
m = n++;
Несмотря на то, что в каждой строке n и модифицируется, и читается, чтение переменной n происходит только для того, чтобы вычислить новое значение n.
Пример N3. Запрещается модифицировать более одного volatile или атомарного объекта.
volatile int volat1, volat2;
_Atomic int atom1, atom2;
int n = atom1++ + atom2++; // <= (A2)
n = (atom1 = 0) + (atom2 = 1); // <= (A2)
n = atom1++ + volat1++; // <= (A1V1)
n = (atom1 = 0) + (volat1 = 1); // <= (A1V1)
n = volat1++ + volat2++; // <= (V2)
n = (volat1 = 0) + (volat2 = 1); // <= (V2)
В этом примере:
- в строках, отмеченных
A2, модифицируются два атомарных объекта; - в строках, отмеченных
A1V1, модифицируется один атомарный и одинvolatile-объект; - в строках, отмеченных
V2, модифицируются дваvolatile-объекта;
Что нарушает пункт N3.
Пример корректного кода:
volat1++;
n = atom1++;
n = (volat1 = 0) + atom1;
n = (volat1 = 0, volat2 = 1); // <= (SP)
m = (volat1 = n) || (volat2 = n + 1); // <= (SP)
В каждой строке, отмеченной SP, модификации происходят не между соседними точками следования.
Пример N4. Запрещается читать более одного volatile-объекта или один и тот же volatile-объект больше одного раза:
volatile int volat1, volat2;
_Atomic int atom1, atom2;
int n = volat1 + volat1; // <= (V1)
n = volat1 + volat2; // <= (V2)
foo(volat1, volat1); // <= (V1)
foo(volat1, volat2); // <= (V2)
В этом примере в операциях сложения, а также при передаче по копии аргументов в вызов функции происходит чтение volatile-объектов.
При этом
- на строках, отмеченных как
V1, происходит два чтения одного и того жеvolatile-объекта; - на строках, отмеченных как
V2, происходит чтение двухvolatile-объектов;
Что нарушает пункт N4.
Пример корректного кода:
n = volat1;
volat1 = volat2;
volat1 = volat1 + 42;
foo(volat1, n);
Пример N5. Запрещается читать более одного раза один и тот же атомарный объект.
_Atomic int atom1, atom2;
int n = atom1 + atom1;
foo(atom1, atom1);
n = foo(atom1, 0) + atom1;
В этом примере в операциях сложения, а также при передаче аргументов по копии в вызов функции происходит чтение атомарных объектов, что нарушает пункт N5 правила.
Пример корректного кода:
n = atom1 + atom2;
atom1 = atom1 + 42;
n = atom1 && atom1; // <= (SP)
n = foo(atom1, 0), atom1; // <= (SP)
В каждой строке, отмеченной как SP, чтение происходит не между соседними точками следования.
Данная диагностика классифицируется как:
|