无符号类型的大位左移

4

类似于以下内容:

uint32_t foo = 1;

foo = foo << 33;

C中的未定义行为?


2
uint32_t 是无符号的。添加 unsigned 是无意义的。而且移位是未定义的行为! - too honest for this site
@Olaf,第四段怎么样? - Sourav Ghosh
注意:如果“int”是宽的,比如64位,那么代码会被定义。 - chux - Reinstate Monica
1
@chux:但这样它就不会是 uint32_t 了。 - too honest for this site
1
@Olaf,3号代码仍将被定义为uint32_t foo,但会变成64位的int,然后进行移位操作,最后再赋值给uint32_t - chux - Reinstate Monica
1
@chux:收到。你是对的。我在我的回答中添加了一条注释。欢迎验证。 - too honest for this site
2个回答

10

实际上,移位是一种未定义的行为。请参阅标准(C11,最终草案 N1570,6.5.7p3):

"... 如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则其行为未定义。"

原因:如果移位计数大于或等于参数的宽度,则移位操作在不同的 CPU 架构上可能会有非常不同的行为。这样,标准允许编译器生成最快的代码,而不必考虑边界效应。

请注意:如果int比33位(例如64位)更宽,则情况会有所不同。原因是整数提升首先将uint32_t转换为int,因此移位使用的是(然后更大的)int值。这使得赋值的回转到uint32_t,参见6.3.1.3第1、2段。但是,在大多数现代系统上,int不大于32位。

4
不,它没有。这在接下来的段落中有提到。 - too honest for this site
我记得当我添加那条评论时,它是引用下一段第二句话的 - 不过编辑历史记录似乎不这样认为!抱歉。 - abligh
@P45Imminent:是的,它就是(而且有时我也希望它不是)。请看添加的原因。 - too honest for this site
@P45Imminent:下次在开始争论之前,先看一下标准吧;-) - too honest for this site
@jfs 而 (1u<<(w-1))<<1 的原因是明确的,只是因为这两个表达式不相同。就像 11 / 2 * 2 不同于 11 / (2 / 2) 一样。但对于移位操作,如果架构恰好如此,编译器可以自由地将两个移位表达式折叠成相同的机器代码。请注意,许多 CPU 不支持机器指令中 w 或更高的移位计数。如果它们使用循环进行多次移位(仅使用单比特移位指令),则循环可以忽略计数以节省代码(周期)。 - too honest for this site
显示剩余7条评论

4
这似乎是未定义行为。来自C标准第6.5.7节(WG14 / N1256委员会草案 - 2007年9月7日ISO / IEC 9899:TC3 - 实际上是C99标准):
1. 对于每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负或大于等于提升的左操作数的宽度,则行为未定义。 2. E1 << E2的结果是E1向左移动E2位; 空出的位用零填充。如果E1具有无符号类型,则结果的值为E1×2^E2,模除比结果类型中可表示的最大值多一个。如果E1具有有符号类型且非负值,并且E1×2^E2可以在结果类型中表示,则该值为结果;否则,行为未定义。
关于第3点,由于右操作数的值大于或等于提升的左操作数的宽度,因此表明它是未定义行为。
关于第4点,由于移位是无符号的,因此适用第一句话:如果E1具有无符号类型,则结果的值为E1×2^E2,模除比结果类型中可表示的最大值多一个。这将(但不包括第3点)暗示结果因此为零。
我相信第3点优先于第4点,因此它是(终究)未定义的。
Olaf的答案表明,在C11下也是如此。

@Rup 叹气,你是对的,我想。我已经修复了它。虽然从技术上讲,哪个更优先并不完全明显。 - abligh
1
实际上,3和4并不矛盾,因此您可以同时拥有3和4而不会出现问题;两者都不需要优先考虑。3表示E2必须小于(提升后的)宽度,然后4给出了结果。另一种看待它的方式是当E2大于或等于宽度时,2^E2不能在(提升后的)类型中表示。这也解释了为什么对于有符号的情况,它明确表示E1 x 2^E2必须是可表示的,否则它就是未定义的。有符号类型没有溢出,因此它们允许实现将移位完全实现为E1 2^E2 - MicroVirus

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