16-битная математика в этой программе вызывает неопределенное поведение? - PullRequest
0 голосов
/ 26 июня 2018

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

1 Ответ

0 голосов
/ 26 июня 2018

вызывает ли это неопределенное поведение, если (токен == - 32762) и (ciFirstToken == - 32768)?

token -= ciFirstToken;

НЕТ (длякраткий ответ)

Теперь давайте разберем этот кусочек на части.

1) Согласно expr.ass для составного задания, -=:

Поведение выражения в форме E1 op = 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 :

A prvalue целочисленного типа, отличного от bool, char16_­ t, char32_­t или wchar_t, чей целочисленный коэффициент преобразования меньше, чем ранг int, можно преобразоватьв prvalue типа int, если int может представлять все значения типа источника;

4) После целочисленного продвижения, дальнейшее преобразование не требуется

Согласно expr.arith.conv / 1.5.1

Если оба операнда имеют одинаковый тип, дальнейшее преобразование не выполняетсяНужен.

5) Наконец, Неопределенное поведение для выражений определяется согласно expr.pre :

Яf во время вычисления выражения результат не определен математически или не находится в диапазоне представимых значений для его типа, поведение равно undefined


ЗАКЛЮЧЕНИЕ

Итак, теперь подставляем значения:

token = -32762 - (-32768);

После всех целочисленных повышений оба операнда попадают в допустимый диапазон INT_MIN [1] и INT_MAX [2] .

И после оценки математический результат (6) равензатем неявно преобразуется в short, что находится в допустимом диапазоне short.

Таким образом, выражение правильно сформировано .

Большое спасибо @MSalters, @nm и @Arne Vogel за помощь в этом ответе.


Visual Studio 2015 MSVC14 Целочисленные ограничения и MS Целочисленные ограничения определяет:

[1] INT_MIN -2147483648
[2] INT_MAX + 2147483647

SHRT_MIN –32768
SHRT_MAX + 32767

...