На днях я обновил свою среду сборки Windows с MSVC2013 до MSVC2017, и вот, функция в моей программе, которая работала отлично в течение многих лет (и все еще работает нормально при g ++ / clang), неожиданно начала давать неправильные результаты при компиляции с MSVC2017 ,
Я был в состоянии переписать функцию, чтобы снова получить правильные результаты, но опыт заставил меня любопытно — была ли моя функция вызывать неопределенное поведение (это только что дало правильные результаты до сих пор), или был код хорошо определен, и MSVC2017 находился в процессе глючит?
Ниже приведена тривиальная программа, показывающая игрушечную версию функции до и после ее переписывания. В частности, вызывает ли функция Maybe_invokes_undefined_behavior (), как показано ниже, неопределенное поведение при вызове с аргументом значения -32762?
#include <stdio.h>
enum {ciFirstToken = -32768};
// This function sometimes gives unexpected results under MSVC2017
void maybe_invokes_undefined_behavior(short token)
{
if (token >= 0) return;
token -= ciFirstToken; // does this invoke undefined behavior if (token==-32762) and (ciFirstToken==-32768)?
if (token == 6)
{
printf("Token is 6, as expected (unexpected behavior not reproduced)\n");
}
else
{
printf("Token should now be 6, but it's actually %i\n", (int) token); // under MSVC2017 this prints -65530 !?
}
}
// This function is rewritten to use int-math instead of short-math and always gives the expected result
void allgood(short token16)
{
if (token16 >= 0) return;
int token = token16;
token -= ciFirstToken;
if (token == 6)
{
printf("Token is 6, as expected (odd behavior not reproduced)\n");
}
else
{
printf("Token should now be 6, but it's actually %i\n", (int) token);
}
}
int main(int, char **)
{
maybe_invokes_undefined_behavior(-32762);
allgood(-32762);
return 0;
}
это вызывает неопределенное поведение if (token == — 32762) и
(CiFirstToken == — 32768)?
token -= ciFirstToken;
НЕТ (для краткого ответа)
Теперь давайте разберем это по частям.
1) согласно expr.ass для составного назначения, -=
:
Поведение выражения формы
E1
оп =E2
эквивалентно
E1 = E1 op E2
Кроме этогоE1
оценивается только один раз.
выражение:
token -= ciFirstToken;
эквивалентно:
token = token - ciFirstToken;
// ^ binary (not unary)
2) аддитивный оператор (-
) выполняет обычное арифметическое преобразование для операндов арифметического типа.
Согласно expr.arith.conv / 1
Многие бинарные операторы, которые ожидают операнды арифметики или
Тип перечисления вызывает преобразования и приводит к типам результата в аналогичном
путь. Цель состоит в том, чтобы получить общий тип, который также является типом
результат. Эта модель называется обычные арифметические преобразования,
которые определены следующим образом:(1.5) В противном случае интегральные продвижения должны быть выполнены для обоих операндов.
3) Оба операнда затем переводятся в int
,
Согласно conv.prom / 1:
prvalue целочисленного типа, кроме
bool
,char16_
т,char32_t
, или же
wchar_t, чей ранг целочисленного преобразования меньше, чем рангint
может быть преобразован в prvalue типаint
еслиint
может представлять все
значения типа источника;
4) После целочисленного продвижения дальнейшее преобразование не требуется.
Согласно expr.arith.conv / 1.5.1
Если оба операнда имеют одинаковый тип, дальнейшее преобразование не требуется.
5) Наконец, Неопределенное поведение для выражений определяется согласно expr.pre:
Если во время вычисления выражения результат не
математически определено или не в диапазоне представимых значений для
его тип, поведение не определено
ЗАКЛЮЧЕНИЕ
Итак, теперь подставим значения:
token = -32762 - (-32768);
После всех целочисленных повышений оба операнда попадают в допустимый диапазон INT_MIN[1] а также INT_MAX[2].
И после оценки математический результат (6) косвенным образом конвертировано в short
, который находится в пределах допустимого диапазона short
,
Таким образом, выражение хорошо сформированные.
Большое спасибо @MSalters, @ n.m и @Arne Vogel за помощь в ответе.
Visual Studio 2015 MSVC14 Целочисленные ограничения а также MS Integer Limits определяет:
[1] INT_MIN -2147483648
[2] INT_MAX +2147483647
SHRT_MIN –32768
SHRT_MAX +32767
Других решений пока нет …