为什么(1 <> 31导致-1?

int z = 1; z <>= 31; printf ("%d\n",z); 

当我运行代码时, z=-1 ,为什么?

 int z = 1; z <<= 31; 

假设int是32位且使用了二进制补码表示,左移位是C中的未定义行为,因为如果在int类型中无法表示结果。 从标准:

E1 << E2的结果是E1左移E2位位置

...

如果E1具有带符号类型和非负值,并且E1×2 E2可在结果类型中表示,那么这就是结果值; 否则,行为未定义。

在实践中,它可能导致0x80000000 ,其被视为负数。

整数的右移是实现定义的行为:

E1 >> E2的结果是E1右移E2位的位置。

...

如果E1具有带符号类型和负值,则结果值是实现定义的。


C ++中,左移是以类似的方式定义,直到C ++ 14 ,正如@TC所提到的(或者,有一些限制,甚至可能是直到C ++ 11 ,正如@MattMcNabb写的那样)。

但即使定义了左移并且0x8000000是预期值,负数右移的结果仍然是实现定义的。

是的,移位有符号整数和负数是我相信的实现定义。

当你向右移动时,你的实现可能会进行符号位扩展。

因此,它不是从左侧移入零,而是在符号位中移位。 z <<= 31; 可能是将符号位设置为1,然后z >>= 31; 从左边0xFFFFFFFF一个,所以最终得到一个0xFFFFFFFF的位模式,在你的平台上被解释为值-1 (可能使用两个补码)。

假设32位int ,这是C11和C ++ 11中的未定义行为,但是在C ++ 14中定义了实现。

C11§6.5.7/ p4(引用N1570):

E1 << E2的结果是E1左移E2位位置; 腾出的位用零填充。 [...]如果E1具有有符号类型和非负值,并且E1 × 2 E2可在结果类型中表示,那么这就是结果值; 否则,行为未定义。

N3337§5.8[expr.shift] / p2中的C ++ 11规则几乎完全相同。 由于2 31通常不能在带符号的32位int ,因此行为未定义。

C ++14§5.8[expr.shift] / p2(引用N3936;另见CWG第1457期 ):

E1 << E2的值是E1左移E2位位置; 空位是零填充的。 [...]否则,如果E1具有有符号类型和非负值,并且E1×2 E2可在结果类型的相应无符号类型中表示,则转换为结果类型的该值是结果值; 否则,行为未定义。

由于2 31在无符号的32位int是可表示的,因此定义了行为,并将结果转换为( 31int ; 此转换是根据§4.7[conv.integral] / p3实现定义的。 在使用二进制补码的典型系统中,你得到-2 31 ,在这种情况下,随后的右移也是实现定义的,因为值是负的。 如果执行算术移位,则符号位移入,最后为-1

假设你在谈论int在这里是32位或更小。 (如果int较大,则此代码定义良好,导致z1 )。

在C和C ++中, z <<= 31被定义为z = z << 31

在C11中, <<被解释为(6.5.7 / 4):

E1 << E2的结果是E1左移E2位位置; 腾出的位用零填充。 [...]如果E1具有有符号类型和非负值,并且E1 × 2 E2可在结果类型中表示,那么这就是结果值; 否则,行为未定义。

在这种情况下, E1z ,它是1E231 。 但是,2 31在32位int无法表示,其最大值为2 31 - 1,因此行为未定义

当发生未定义的行为时,任何事情都可能发生,从您看到意外输出到程序崩溃,到发射导弹等等。

在C99中,C ++ 98和C ++ 03 <<具有相似的定义; 1 << 31是所有这些中未定义的行为(对于32位或更小的整数)。


在C ++ 11和C ++ 14中,有一个可以测试的选项, std::numeric_limits::is_modulo 。 如果这是true那么在某些人看来,这意味着整数溢出不再是未定义的,因此1 << 31的结果必须是INT_MIN 。 有关此主题的进一步讨论, 请参阅此主题 。

假设我们得到这么多(即你的系统有32位整数, std::numeric_limits::is_modulo == true ,你可以与那些解释标准的人一起说明在签名的int溢出中没有UB在这种情况下)然后我们有以下内容:

 assert( CHAR_BIT * sizeof(int) == 32 ); assert( std::numeric_limits::is_modulo == true ); int z = 1; z <<= 31; assert(z == INT_MIN); 

现在讨论z >>= 31 。 这被定义为z = z >> 31; 。 在C ++ 11 5.8 / 3中:

E1 >> E2的值是E1右移E2位位置。 [...]如果E1具有带符号类型和负值,则结果值是实现定义的。

由于INT_MIN是负值,因此行为是实现定义的。 这意味着您的实现必须记录它的作用,因此您可以查阅编译器的文档以了解此处发生的情况。

一个可能的解释是它执行算术移位 ,这意味着位向右移位,但符号位保留其值。 这意味着你最终得到了所有位 - 一,在二进制补码中为-1

这是因为符号复制机制,当你将z移动31次时,1从第0位移位到第31位。 现在,你在第31位有1,这将被视为负数。 在负数中,使用符号复制机制,如果你右移负数,则保留符号位。 所以你在每个位的位置都是1,小数是-1。

如果你使用unsigned int你会得到相同的结果。 问题是你已经使用<<向左移31位加一位符号31次。 这是无符号行为,因为您已将左侧最重要的位丢失到符号位(这是由于未定义的行为而导致的结果)

当你进行右移时,当你有符号整数(你得到一个符号位到最高有效位的副本)时,这比你没有符号的那样(你从左侧得到一个零位)会有所不同。 通常,这意味着当你执行右移和右逻辑移位指令(相当于除以2但使用无符号数)时,你得到一个有符号整数的算术移位指令(相当于除以2)使用无符号数字进行右移。

只需尝试相同的声明z作为unsigned int z; 你会得到预期的行为。