这个程序中的16位数学是否会调用未定义的行为?

Jer*_*ner 8 c++ undefined-behavior language-lawyer msvc14

有一天,我升级我的Windows从MSVC2013到MSVC2017和LO构建环境,不料,在我的计划已被多年工作正常功能(现在仍然工作在克细++ /铛)突然开始时MSVC2017编译给出不正确的结果.

我能够重写函数再次给出正确的结果,但是经验让我好奇 - 我的函数调用了未定义的行为(直到现在恰好给出了正确的结果),或者是代码定义明确且MSVC2017正在越野车?

下面是一个简单的程序,在重写之前和之后都显示了该功能的玩具版本.特别是,当使用值为-32762的参数调用时,函数maybe_invokes_undefined_behavior(),如下所示,调用未定义的行为吗?

#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;
}
Run Code Online (Sandbox Code Playgroud)

Jos*_* D. 8

如果(token == - 32762)和(ciFirstToken == - 32768),这会调用未定义的行为吗?

token -= ciFirstToken;

不(简答)

现在让我们一块一块地打破这个.

1)按照expr.ass进行复合分配,-=:

形式为E1 op = 的表达式的行为E2等同于 E1 = E1 op E2E1计算一次的表达式.

表达方式:

token -= ciFirstToken;
Run Code Online (Sandbox Code Playgroud)

相当于:

token = token - ciFirstToken;
//            ^ binary (not unary)
Run Code Online (Sandbox Code Playgroud)

2)加法运算符(-)对算术类型的操作数执行通常的算术转换.

根据expr.arith.conv/1

许多期望算术或枚举类型的操作数的二元运算符会以类似的方式引起转换并产生结果类型.目的是产生一个通用类型,它也是结果的类型.这种模式称为通常的算术转换,定义如下:

(1.5)否则,应在两个操作数上执行整体促销.

3)然后将两个操作数提升为int.

根据conv.prom/1:

prvalue以外的整数类型bool,char16_­T,char32_­t或wchar_t的其整转换秩小于的秩int 可以被转换为一个prvalue类型的int,如果int可以表示源类型的所有值;

4)整数提升后,无需进一步转换.

根据expr.arith.conv/1.5.1

如果两个操作数具有相同的类型,则不需要进一步转换.

5)最后,表达式的未定义行为定义为expr.pre:

如果在评估表达式期间,结果未在数学上定义或未其类型的可表示值范围内,则行为未定义


结论

所以现在替换值:

token = -32762 - (-32768);
Run Code Online (Sandbox Code Playgroud)

在所有整数提升之后,两个操作数都在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