16-битная математика в этой программе вызывает неопределенное поведение?

На днях я обновил свою среду сборки 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;
}

8

Решение

это вызывает неопределенное поведение 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

8

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]