在C ++ 03中左移有符号整数未定义的行为吗?

时间:2012-03-26 06:59:02

标签: c++ language-lawyer shift

根据C ++ 03,5.8 / 2,左移定义如下:

  

E1的值<&lt; E2是E1(解释为位模式)左移E2位位置;空位是零填充的。如果E1具有无符号类型,则结果的值为E1乘以上升到功率E2的数量2,如果E1的类型为无符号长,则减少模ULONG_MAX + 1,否则为UINT_MAX + 1。

这里困扰我的是明确提到了无符号类型,但完全忽略了已签名的类型。将其与定义右移的5.8 / 3进行比较:

  

E1的值&gt;&gt; E2是E1右移E2位位置。如果E1具有无符号类型或者E1具有有符号类型和非负值,则结果的值是E1的商除以提升到功率E2的数量2的积分部分。如果E1具有有符号类型和负值,则结果值是实现定义的。

在5.8 / 3中,明确提到有签名和无签名,即使签署持有非负和签名持有负值也单独提及。

AFAIK在C ++ Standard中未明确定义某些内容时,行为未定义。我也看过this question,但它侧重于C和C ++之间的差异,似乎没有人人都会同意的答案。

左移一个在C ++ 03中定义的有符号整数吗?

2 个答案:

答案 0 :(得分:9)

5.8 / 2表示它将其解释为位模式,如果由于某种原因你的实现不使用2的补码,或者如果你的编译器第二次猜测你(他们没有),那么它只是依赖于实现。 C ++ 11更明确,但同样的说法。

有符号整数使用所谓的2的补码。基本上,如果你将有符号整数移位1,如果它是正数且低于2 ^(位 - 2),它将像无符号一样工作。如果它高于该值但是正数,您将创建一个与原始数字无关的奇怪负数。如果它开头是负面的,你可能会得到负数,可能是正数。

例如,如果我们有一个8位有符号整数,表示-1:

11111111 // -1

如果我们左移,我们最终会

11111110 // -2

但是,假设我们有-120

10001000  // -120

我们最终会以

结束
00010000  // 16

显然这不正确!

继续,使用数字65:

01000001  // 65

向左移动,这将成为:

10000001  // -127

等同于-127。

然而,数字16:

00010000 // 16

左移是

00100000 // 32

正如你所看到的,它“有时可行,有时不会”但通常在你的数字低于2 ^(位-2)时有效,有时但如果高于2 ^(位-2 ))。也就是说,向左移动1.向左移动2,再取消一点。等

答案 1 :(得分:9)

我想补充一点,C ++ 11中的规则发生了变化。

在C ++ 11中,负数左侧的带符号左移总是未定义,即使底层机器为范围内的值定义它也是如此。它不是实现定义的,它是 undefined 。这意味着如果你这样做,编译器可以自由地做任何想做的事情,包括意外删除你的一堆代码。这与负数的有符号移位相反,后者是实现定义的,这意味着其结果取决于机器类型。

Clang的-fsanitize=undefined模式试图转移左侧负数。