Быть уверенным в "неизвестном порядке оценки"
С версии 1.80 Cppcheck сообщает мне, что
Выражение "msg[ipos++]= контрольная сумма (&msg[1],ipos-1)" зависит от порядка оценки побочных эффектов
в этой последовательности кода (упрощенно, data
переменная)
BYTE msg[MAX_MSG_SIZE]; // msg can be smaller, depending on data encoded
int ipos = 0;
msg[ipos++] = MSG_START;
ipos += encode(&msg[ipos], data);
msg[ipos++] = checksum(&msg[1], ipos-1); // <---- Undefined Behaviour?
msg[ipos++] = MSG_END; // increment ipos to the actual size of msg
и рассматривает это как ошибку, а не проблему переносимости.
Это C-код (встроенный в C++-доминируемый проект), скомпилированный с помощью C++98-совместимого компилятора, и, между тем, он работает десятилетиями, как и ожидалось. Cppcheck запускается с C++03, C89, автоопределение языка.
Признаюсь, что код лучше переписать. Но перед этим я попытаюсь выяснить: действительно ли это зависит от порядка оценки? Насколько я понимаю, сначала проверяется правильный операнд (это нужно сделать перед вызовом), затем происходит присвоение (для msg[ipos]
) с приращением ipos
сделано в прошлом.
Я ошибаюсь с этим предположением, или это просто ложный положительный результат?
2 ответа
Этот код действительно зависит от порядка оценки способом, который не очень хорошо определен:
msg[ipos++] = checksum(&msg[1], ipos-1);
В частности, не указано, ipos++
будет увеличиваться до или после ipos-1
оценивается. Это связано с тем, что на =
только в конце полного выражения (;
).
Вызов функции является точкой последовательности. Но это только гарантирует, что ipos-1
происходит до вызова функции. Это не гарантирует, что ipos++
происходит после.
Похоже, код должен быть переписан следующим образом:
msg[ipos] = checksum(&msg[1], ipos-1);
ipos++; // or ++ipos
Порядок оценки операндов =
не указано Итак, для начала, код опирается на неопределенное поведение.
Что делает дело еще хуже, так это то, что ipos
используется дважды в одном и том же выражении, без промежуточной точки, для несвязанных целей - что приводит к неопределенному поведению.
C99 6.5
Между предыдущей и следующей точкой последовательности объект должен иметь свое сохраненное значение, измененное не более одного раза путем оценки выражения. Кроме того, предыдущее значение должно быть только для чтения, чтобы определить значение, которое будет сохранено.
Тот же текст относится к C90, C99, C++98 и C++03. В C11 и C++11 формулировка изменилась, но смысл тот же. Это неопределенное поведение, pre-C++11.
Компилятор не обязан предоставлять диагностику для неопределенного поведения. Тебе повезло, что это сделал. Это не ложный положительный результат - ваш код содержит серьезную ошибку, начиная с исходного кода на языке Си.