为什么要先左移位然后右移位,而不是使用AND运算符?

6

我看到了这段C代码:

typedef int gint
// ...
gint a, b;
// ...
a = (b << 16) >> 16;

为了方便表示,假设此时 b = 0x11223344。据我所见,它执行以下操作:
  • b << 16 将得到 0x33440000
  • >> 16 将得到 0x00003344
因此,最高的 16 位被丢弃了。
如果 b & 0x0000ffff 也能起作用,为什么有人要写 (b << 16) >> 16 呢?后者难道不更容易理解吗?在这种情况下使用位移是否有任何原因?是否存在任何边缘情况使得这两种方法不能相同?

1
@KerrekSB 起初我认为一个不太优化的编译器可能会对位移操作使用更多的指令,但重新考虑后,情况应该不是这样的。谢谢。 - user1544337
1
行为可能会因为它是有符号的还是无符号的而改变。对于 uint32_t,这与 & 掩码相同。对于 int,在右移时可能会得到符号扩展。那么 gint 是什么? - Useless
3
尝试使用-1或0x8000,并注意&不同于*。利用未定义的符号扩展行为可能是有意的。 - Hans Passant
5
最后一次更改从unit32_t到int相当彻底,这是一个非常严重的错误,现在你已经得到了答案,这会有所不同。在原作者澄清情况之前,我不会在此问题中添加答案。 - Shafik Yaghmour
1
在C语言中对负数进行右移位操作 - phuclv
显示剩余8条评论
3个回答

3
假设int类型的大小为32位,则不需要使用移位操作。实际上,按位与一个掩码会更具可读性、移植性和安全性。
需要注意的是,在负的有符号整数上左移会调用未定义的行为,而将东西左移进有符号整数的符号位也可能会调用未定义的行为。C11 6.5.7(我强调):
“E1 << E2”的结果是E1向左移动E2个位置;空出的位用零填充。如果E1具有无符号类型,则结果的值为E1×2E2,对结果类型中可表示的最大值再加一后取模。如果E1具有有符号类型且非负值,并且E1×2E2在结果类型中可表示,则该值为结果;否则,行为未定义。”
(我唯一能想到的理由是,为了优化某个带有糟糕编译器的16位CPU。然后,如果你将算术分解成16位块,代码会更有效率。但在这种系统上,int很可能是16位,那么代码就没有意义了。)
顺便说一句,使用有符号的int类型也没有意义。这段代码最正确和安全的类型应该是uint32_t

1
我喜欢这个回答,因为它在OP改变问题的情况下对其进行了批评,这让回答者感到沮丧。但是,我认为你应该批评“int”为32位的假设,肯定不是“可移植”的。尽管如此,“&”本身比移位更可取,以避免未定义的行为。 - Veltas
有符号左移在标准中是未定义的。由于 UB 的部分目的是允许实现上的自由度,它仍然可以由平台定义。实际上,gcc 已经规定了其行为,包括符号扩展。详见:https://gcc.gnu.org/ml/gcc/2000-04/msg00152.html - Useless
另一个你可能会使用的理由是,对于无符号的左操作数,无论操作数的大小如何,高16位都会被清除。我暂时想不到一个使用案例,在这种情况下你可能会改变操作数的大小。无论如何,我会删除自己的答案,因为在原始问题编辑后它变得多余了。 - Veltas

1
对于无符号整数类型(例如,我们最初认为使用的uint32_t),
(b << 16) >> 16

is等同于b & (1<<16 - 1)

对于有符号整数类型来说,

(b << 16)

可能会变成负数(即,单独考虑低位的 int16_t 被视为负数),在这种情况下

(b << 16) >> 16

由于符号扩展,将(很可能仍然)是负数。在这种情况下,它与&掩码不同,因为高位将被设置为1而不是0。


要么这种行为是有意的(在这种情况下,被注释的typedef是误导性的),要么这是一个错误。如果没有阅读代码,我无法确定。
哦,而且在两个方向上的移位行为是我期望gcc在x86上表现的方式,但我不能评论它在其他地方的可移植性。Lundin指出左移可能是未定义行为,在右移上的符号扩展是实现定义的。

我不是要注释掉那个typedef的,抱歉。 - user1544337
哦,我以为uint32_t的typedef在代码中被注释掉了。无论如何,没关系。 - Useless
1
“由于符号扩展,仍将为负数”在这种情况下,代码会调用未定义和实现定义的行为。无法预测这样的代码会做什么。 - Lundin
同意,我添加了一些关于可移植性限制的细节。 - Useless
1
如果“int”是16位,则“1<<16”是未定义行为。使用“(uint32_)1 << 16”才是正确的。 - too honest for this site

1
所以,前16位被丢弃了。 它们并没有被丢弃。尽管有正式的实现定义规定如何对有符号类型执行右移操作,但大多数编译器都会复制符号位进行操作。 因此,这个表达式的结果是用第15位的复制值填充了前16位的最高位。

规范中确实定义了带有非负值的有符号类型的右移操作。 - davmac
@Lundin:该问题并不排除右移产生负数的可能性。 - Veltas
@davmac 是的,正确的表述应该是这样的:从正式意义上讲,右移操作在有符号整数类型的定义域上只有部分被定义;因此,完整的定义取决于实现方式 - ach
@Lundin,我在标准中找不到将数据左移至符号位导致未定义行为的任何措辞。您能提供一些证明吗? - ach
@Lundin,我很好奇,于是查了一下C++标准,但并没有这样的条款。我不知道C在这方面有所不同。显然,这是两种语言之间又一个无法解释的微小差异。 - ach
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接