我刚写了这些代码:
int x = -1;//x must be negative
unsigned int y = 1;//y must be positive
bool b;
for(; ; x--, y++){
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
}
然后我发现b
总是如此。在我看来,((unsigned int)x)* y会溢出,但((unsigned int)(x * y))不会。我真的很难相信这是真的。这只是巧合还是这种现象背后有任何法律?
答案 0 :(得分:8)
在x * y
中,由于通常的算术转换,x
已转换为unsigned
。 §5/ 10:
即。您的第一个表达式(unsigned)(x * y)
相当于(unsigned)((unsigned)x * y)
,而(unsigned)x * y
相当于unsigned int
- 您的第二个表达式。
请注意,signed
的等级等于(int
)RequireJS
的等级§4.13/ 1.4:
任何无符号整数类型的等级应等于等级 相应的有符号整数类型。
答案 1 :(得分:3)
这只是巧合还是这种现象背后有任何法律?
不,这不是巧合,这可以通过通常的算术转换执行的隐式转换来解释。
乘法运算符对其操作数执行usual arithmetic conversions,以使它们成为公共类型和此子表达式:
(unsigned int)(x * y)
将导致x
转换为unsigned int。这使得:
(unsigned int)(x * y)
相当于:
((unsigned int)x) * y
很多时候使用正确的警告标志可以帮助解决难题,使用-Wall
和gcc它会发出以下警告:
warning: self-comparison always evaluates to true [-Wtautological-compare]
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
^
在clang中使用-Wconversion
标志会发出以下警告( see it live ):
warning: implicit conversion changes signedness: 'int' to 'unsigned int' [-Wsign-conversion]
b = ((unsigned int)x) * y == ((unsigned int)(x * y));
^ ~
供参考,草案C ++标准部分5.6
[expr.mul] 表示:
通常的算术转换是在操作数上执行的 并确定结果的类型。
涵盖常规算术转化的部分5
表示:
否则,应对两个操作数执行整数提升(4.5)。 61 然后以下 规则应适用于推广的操作数:
[...]
- 否则,如果具有无符号整数类型的操作数的等级大于或等于 另一个操作数的类型的等级,带有符号整数类型的操作数应转换为 具有无符号整数类型的操作数的类型。
我们可以在4.13
[em.rank] 部分找到cppreference here及C ++标准草案所涵盖的排名:
任何无符号整数类型的等级应等于相应的有符号整数类型的等级。
答案 2 :(得分:1)
这里有两个要点:
通常的算术转换
无符号整数运算不能溢出
引用标准:
§5表达式[expr]
9许多期望操作数的二元运算符 算术或枚举类型会导致转换和产生结果 以类似的方式输入类型。目的是产生一种共同的类型 也是结果的类型。这种模式通常被称为 算术转换,定义如下:
[...]
- 否则,如果具有无符号整数类型的操作数的等级大于或等于 另一个操作数的类型的等级,带有符号整数类型的操作数应转换为 具有无符号整数类型的操作数的类型。
所以(第1点),两个操作数具有相同的等级,有符号的操作数将被转换为无符号类型。因此,((unsigned int)x) * y
和((unsigned int)(x * y))
评估始终相同。
现在让我们看看它们是否始终有效。
§3.9.1基本类型[basic.fundamental]
4无符号整数,声明无符号整数,应遵守算术模2n的定律,其中n是数字 特定大小为整数的值表示中的位数.46
46)这意味着无符号算术不会溢出,因为结果无法表示 无符号整数类型以模数减少,该数字大于可由其表示的最大值 得到无符号整数类型。
所以(第2点),((unsigned int)x) * y
将不溢出(y
也不会x
,因为for循环继续,所以未定义行为将会发生)。