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

yvt65v4c  于 2023-01-10  发布在  其他
关注(0)|答案(2)|浏览(180)

根据C03,5.8/2,左移定义如下:
E1〈〈E2的值是E1(被解释为比特模式)左移E2比特位置;如果E1为无符号类型,则结果值为E1乘以数量2的E2次幂,如果E1为无符号长型,则以ULONG_MAX+1为模,否则以UINT_MAX+1为模。
这里让我困扰的是无符号类型被显式地提到了,而有符号类型却被完全忽略了,这与5.8/3中定义的右移比较一下:
E1〉〉E2的值是E1右移E2位的位置。如果E1为无符号类型或E1为有符号类型且为非负值,则结果值为E1除以数量2的E2次幂所得商的整数部分。如果E1为有符号类型且为负值,则结果值由实现定义。
在5.8/3中,有符号和无符号都被明确提及,甚至有符号的非负值和有符号的负值也被分别提及。
AFAIK当一些东西没有在C
标准中显式定义时,行为是未定义的。我也看过this question,但它关注的是C和C之间的差异,似乎没有一个每个人都会同意的答案。
左移是C
03中定义的有符号整数吗?

sdnqo3pr

sdnqo3pr1#

5.8/2表示它将其解释为位模式,这仅在由于某种原因您的实现没有使用2的补码,或者您的编译器猜测您(他们没有)时才与实现相关。C++11更明确,但表示相同的事情。
有符号整数使用的是2的补码。基本上,如果你将一个有符号整数移位1位,如果它是正的并且小于2^(位-2),那么它就像是一个无符号整数一样工作;如果它大于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^(bits-2)时有效,在大于-(2^(bits-2))时有时有效,也就是说,左移1位,左移2位,去掉另一位,等等。

s2j5cfk0

s2j5cfk02#

我想补充的是规则在C++11中发生了变化。

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

Clang的-fsanitize=undefined模式捕获向左移负数的尝试。

相关问题