有符号数和无符号数的相减后再除法运算

25
以下结果让我感到非常困惑:
int i1 = 20-80u;    // -60
int i2 = 20-80;     // -60
int i3 =(20-80u)/2; // 2147483618
int i4 =(20-80)/2;  // -30
int i5 =i1/2;       // -30
  1. i3 看起来是计算为 (20u-80u)/2,而非 (20-80u)/2
  2. 据说 i3i5 是相同的。
3个回答

13

据我所知,有符号整数和无符号整数之间的算术运算会产生一个无符号结果。

因此,20 - 80u 会产生等于 -60 的无符号结果:如果 unsigned int 是一个32位类型,那么结果将是4294967236。

顺便提一下,将其分配给i1会产生一个实现定义的结果,因为这个数字太大了无法容纳。得到 -60是典型的,但并不保证。


5
顺带一提,将该值赋给i1是未定义的行为。你确定吗?我教过的是从无符号整数转换为有符号整数对于所有无符号整数的值都是定义良好的。 - rozina
4
这里没有有符号整数溢出,只有类型转换。请参见conv.integral - autistic
@rozina:哎呀,我以前从来没有见过这种转换在这方面的表现是不同的。已修复。 - user1084944

10
int i1 = 20-80u;    // -60
这里存在微妙的陷阱! 由于操作数不同,需要进行转换。两个操作数都将被转换为一个公共类型(在此情况下为无符号整数)。结果将是一个大的无符号整数值(如果我的计算正确,则比UINT_MAX + 1少60),然后再将其转换为int并存储在i1中。由于该值超出了int的范围,因此结果将是实现定义的,可能是一个陷阱表示形式,因此在尝试使用时可能导致未定义的行为。但是,在您的情况下,它巧合地转换为-60
int i3 =(20-80u)/2; // 2147483618
继续第一个例子,我猜测 20-80u 的结果比 UINT_MAX + 1 少60。如果 UINT_MAX 是4294967295(一个常见的 UINT_MAX 值),那就意味着 20-80u4294967236……而 4294967236 / 2 则是 2147483618。
对于 i2 和其他值,应该不会有什么意外。它们遵循传统的数学计算,没有转换、截断、溢出或任何其他实现定义的行为。

如果我理解正确的话,将-1转换为无符号数是有定义的,结果是UINT_MAX。但是,如果您将UINT_MAX转换回int,则突然变成了实现定义?并且可能不是-1吗? - rozina
好的回答日 :) - LPs

3
二进制算术运算符将对其操作数执行通常的算术转换以将它们带到一个公共类型。在i1i3i5的情况下,公共类型将是unsigned int,因此结果也将是unsigned int。无符号数字将通过模算术进行包装,因此减去稍大的无符号值将导致接近无法用int表示的无符号int max的数字。因此,在i1的情况下,我们最终得到一个实现定义的转换,因为该值无法表示。在i3的情况下,除以2将无符号值带回int范围,因此我们最终得到一个大的有符号int值。相关的C++草案标准部分如下。第5.7[expr.add]:
通常的算术转换在第5节中介绍,其中提到:
许多二元运算符希望操作数为算术或枚举类型,并以类似的方式进行转换并产生结果类型。其目的是产生一个公共类型,该类型也是结果的类型。这种模式称为通常的算术转换,定义如下:
[...]
否则,如果具有无符号整数类型的操作数的等级大于或等于另一个操作数的类型的等级,则具有有符号整数类型的操作数应转换为具有无符号整数类型的操作数的类型。
对于不能表示有符号类型的值的转换,第4.7[conv.integral]中提到:
如果目标类型为有符号类型,则如果它可以用目标类型(和位域宽度)表示,则该值不变;否则,该值是实现定义的。
对于无符号整数遵循模算术,第3.9.1[basic.fundamental]中提到:
“无符号整数应当遵循算术模2n的规则,其中n是该特定大小整数值表示中的位数。48。”

@Hurkyl:该死,我今天站着睡觉了,我搞砸了无符号溢出和从无符号转换为有符号(后者是实现定义的)。我会自毁我的评论... - Matthieu M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接