8

前几天我将我的 Windows 构建环境从 MSVC2013 升级到 MSVC2017,你瞧,我的程序中一个多年来一直运行良好的函数(在 g++/clang 下仍然运行良好)在使用 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;
}
4

1 回答 1

8

如果(token==-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

一个除, t, , 或wchar_t以外的整数类型的纯右值,其整数转换秩小于 的秩,如果可以表示源类型的所有值,则 可以转换为类型的纯右值;boolchar16_­char32_­tintintint

4) 整数提升后,无需再进行转换。

根据expr.arith.conv/1.5.1

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

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

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


结论

所以现在替换值:

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

于 2018-06-26T11:51:22.637 回答