该程序中的 16 位数学是否调用了未定义的行为?
Is the 16-bit math in this program invoking undefined behavior?
前几天,我将 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;
}
does this invoke undefined behavior if (token==-32762) and
(ciFirstToken==-32768)?
token -= ciFirstToken;
否(简短回答)
现在让我们逐条分解。
1) 根据 expr.ass 复合赋值,-=
:
The behavior of an expression of the form E1
op= E2
is equivalent to
E1 = E1 op E2
except that E1
is evaluated only once.
表达式:
token -= ciFirstToken;
相当于:
token = token - ciFirstToken;
// ^ binary (not unary)
2) additive operator (-
) performs usual arithmetic conversion 用于算术类型的操作数。
Many binary operators that expect operands of arithmetic or
enumeration type cause conversions and yield result types in a similar
way. The purpose is to yield a common type, which is also the type of
the result. This pattern is called the usual arithmetic conversions,
which are defined as follows:
(1.5) Otherwise, the integral promotions shall be performed on both operands.
3) 然后两个操作数都被提升为 int
.
根据conv.prom/1:
A prvalue of an integer type other than bool
, char16_
t, char32_t
, or
wchar_t whose integer conversion rank is less than the rank of int
can be converted to a prvalue of type int
if int
can represent all the
values of the source type;
4) 整数提升后,不需要进一步转换
If both operands have the same type, no further conversion is needed.
5) 最后,表达式的 未定义行为 定义为 expr.pre:
If during the evaluation of an expression, the result is not
mathematically defined or not in the range of representable values for
its type, the behavior is undefined
结论
所以现在替换值:
token = -32762 - (-32768);
所有整数提升后,两个操作数都在INT_MIN[1]和[=的有效范围内87=]INT_MAX[2].
然后求值后,数学结果(6)被隐式转换为short
,在short
的有效范围内。
因此,表达式格式正确。
非常感谢@MSalters、@n.m 和@Arne Vogel 帮助回答这个问题。
Visual Studio 2015 MSVC14 Integer Limits and MS Integer Limits 定义:
[1] INT_MIN -2147483648
[2] INT_MAX +2147483647
SHRT_MIN –32768
SHRT_MAX +32767
前几天,我将 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;
}
does this invoke undefined behavior if (token==-32762) and (ciFirstToken==-32768)?
token -= ciFirstToken;
否(简短回答)
现在让我们逐条分解。
1) 根据 expr.ass 复合赋值,-=
:
The behavior of an expression of the form
E1
op=E2
is equivalent toE1 = E1 op E2
except thatE1
is evaluated only once.
表达式:
token -= ciFirstToken;
相当于:
token = token - ciFirstToken;
// ^ binary (not unary)
2) additive operator (-
) performs usual arithmetic conversion 用于算术类型的操作数。
Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:
(1.5) Otherwise, the integral promotions shall be performed on both operands.
3) 然后两个操作数都被提升为 int
.
根据conv.prom/1:
A prvalue of an integer type other than
bool
,char16_
t,char32_t
, or wchar_t whose integer conversion rank is less than the rank ofint
can be converted to a prvalue of typeint
ifint
can represent all the values of the source type;
4) 整数提升后,不需要进一步转换
If both operands have the same type, no further conversion is needed.
5) 最后,表达式的 未定义行为 定义为 expr.pre:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined
结论
所以现在替换值:
token = -32762 - (-32768);
所有整数提升后,两个操作数都在INT_MIN[1]和[=的有效范围内87=]INT_MAX[2].
然后求值后,数学结果(6)被隐式转换为short
,在short
的有效范围内。
因此,表达式格式正确。
非常感谢@MSalters、@n.m 和@Arne Vogel 帮助回答这个问题。
Visual Studio 2015 MSVC14 Integer Limits and MS Integer Limits 定义:
[1] INT_MIN -2147483648
[2] INT_MAX +2147483647
SHRT_MIN –32768
SHRT_MAX +32767