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

Is left-shifting a signed integer undefined behavior in C++03?

本文关键字:符号 整数 左移 C++03 未定义      更新时间:2023-10-16

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

E1<lt;E2是E1(解释为比特模式)左移位的E2比特位置;空出的比特被零填充。如果E1具有无符号类型,则结果的值为E1乘以数量2的幂E2,如果E1具有类型unsigned long,则减模ULONG_MAX+1,否则为UINT_MAX+1。

这里困扰我的是,显式地提到了无符号类型,而完全忽略了有符号类型。将其与定义右移的5.8/3进行比较:

E1>>E2的值是E1右移E2位的位置。如果E1具有无符号类型,或者如果E1具有有符号类型和非负值,则结果的值是E1的商除以量2的整数部分,该量是E2的幂。如果E1具有带符号类型和负值,则生成的值是实现定义的。

在5.8/3中,有符号和无符号都被明确提及,甚至有符号持有非负值和有符号持有负值也被单独提及。

AFAIK当某些东西在C++标准中没有明确定义时,行为是未定义的。我也看到过这个问题,但它关注的是C和C++之间的差异,似乎没有一个每个人都会同意的答案。

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

5.8/2表示,它将其解释为一个位模式,只有当您的实现出于某种原因没有使用2的补码,或者编译器再次猜测您(他们没有)时,它才与实现相关。C++11更明确,但也表达了同样的观点。

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

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

11111111 // -1

如果我们离开轮班,我们最终会得到

11111110 // -2

然而,假设我们有-120

10001000  // -120

我们最终会得到

00010000  // 16

显然这是不对的!

继续,使用数字65:

01000001  // 65

向左移动,这将变成:

10000001  // -127

相当于-127。

然而,数字16:

00010000 // 16

向左移动是

00100000 // 32

正如你所看到的,它"有时有效,有时无效",但如果你的数字低于2^(bits-2),它通常有效,如果它高于-(2^(bit s-2)),它有时无效。也就是说,向左移动1。要左移2,再减去一位。等等。

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

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

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