int z = 1;
z <<= 31;
z >>= 31;
printf ("%d\n",z);
当我运行代码时,为什么
z=-1
?int z = 1;
z <<= 31;
int
是32位的并且使用二进制补码表示,左移在C语言中是未定义的行为,因为结果如果不能用int
类型来表示。 标准如下:
实际上,它很可能会导致E1 << E2 的结果是将E1向左移动E2个位置
...
如果E1具有带符号类型和非负值,并且E1 × 2E2可以在结果类型中表示,则该值为结果;否则,行为未定义。
0x80000000
,这被视为负数。0x8000000
,负数的右移结果仍然是实现定义的。x>>31
优化为 x=0
。同样,它可以将 x<<31
视为证明 x<=0
(以及 x>=-1
可能?不确定),因为其他任何行为都会导致未定义的行为。 - Yakk - Adam Nevraumont没错,对于带符号整数和负数的右移操作,具体实现是由编译器决定的。
你使用的实现可能会在右移时进行符号位扩展。
因此,它不是从左边移入零,而是移入符号位。z <<= 31;
可能将符号位设置为1,然后 z >>= 31;
从左侧移入1,这样你最终得到的位模式是 0xFFFFFFFF
,在你的平台上被解释为值为 -1
(该平台可能使用二进制补码)。
int
,在C11和C++11中这是未定义行为,但在C++14中是实现定义的。E1 << E2
is E1
left-shifted E2
bit positions;
vacated bits are filled with zeros. [...] If E1
has a signed type
and nonnegative value, and E1 × 2
E2
is representable in
the result type, then that is the resulting value; otherwise, the
behavior is undefined."int
,因此行为未定义。E1 << E2
is E1
left-shifted E2
bit positions;
vacated bits are zero-filled. [...] Otherwise, if E1
has a signed
type and non-negative value, and E1×2
E2
is representable
in the corresponding unsigned type of the result type, then that
value, converted to the result type, is the resulting value;
otherwise, the behavior is undefined."int
,因此行为是定义的,并且结果是2的31次方转换为(有符号)int
;根据§4.7 [conv.integral]/p3,此转换是实现定义的。在使用二进制补码的典型系统中,您将得到-2的31次方,此后右移也是实现定义的,因为该值为负数。如果执行算术移位,则符号位被移入,最终得到-1
。这是因为符号复制机制,每当您将 z 左移 31 次时,1 就从第 0 位位置移到第 31 位位置。现在您在第 31 位上有 1,这将被视为负数。在负数中使用符号复制机制,即如果您对负数进行右移,则会保留符号位。所以,在每个位位置上,您都有 1,这在十进制中为 -1。
如果您使用的是unsigned int
,则会得到相同的结果。问题在于您使用了<<
将31位加上一个符号位左移31次。这是无符号行为,因为您已经失去了左侧最高位进入符号位(这就是您得到未定义行为结果的原因)
当您进行右移时,当您有带符号整数时,这是以不同方式进行的(您将符号位复制到最高位),而当您有无符号整数时,则从左侧获取零位移位。通常,这意味着当您使用带符号整数进行右移时,您会得到一个右算术移位指令(相当于除以二),而当您使用无符号数字进行右移时,您会得到一个右逻辑移位指令(也相当于除以二,但使用无符号数字)。
只需将z声明为unsigned int z;
,您将获得预期的行为。
假设您现在谈论的是int
为32位或更小的情况。(如果int
更大,则此代码定义良好,并导致z
为1
)。
在C和C++中,z <<= 31
被定义为z = z << 31
。
在C11中,<<
解释为(6.5.7/4):
在这种情况下,
E1 << E2
的结果是将E1
左移E2
个位位置;空出的位以零填充。[...] 如果E1
具有有符号类型并且非负值,并且E1
×2
E2
可以用结果类型表示,则这就是结果值;否则,行为未定义。
E1
是 z
,即 1
,而 E2
是 31
。然而,231 在一个最大值为 231-1 的32位 int
中是不可表示的,因此行为未定义。<<
具有类似的定义;对于32位或更小的整数,1 << 31
在所有这些情况下都是未定义的行为。
std::numeric_limits<int>::is_modulo
。如果这是true
,则在某些人的看法中,这意味着整数溢出不再是未定义的,因此 1 << 31
的结果必须是 INT_MIN
。有关此主题的进一步讨论,请参见此线程。
假设我们走到了这一步(即您的系统具有32位整数,且std::numeric_limits<int>::is_modulo == true
,并且您支持那些解释标准的人表示在这种情况下有没有符号整数溢出的 UB),则我们有以下内容:
assert( CHAR_BIT * sizeof(int) == 32 );
assert( std::numeric_limits<int>::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,这在二进制补码中是-1
。
>>
可能更有意义。 - Paul R