我知道 C 语言中的以下代码:
(1 << 31)
根据 C 标准 ( ),被视为未定义行为:
如果右操作数为负或者其值大于 int 中的位数,则移位运算的结果未定义。
我只是好奇以下 C 代码是否:
(1LL << 63)
在任何 C 标准中也被视为未定义行为。
7
最佳答案
3
C23说(我强调):
的结果
E1 << E2
是E1
左移的E2
位位置;空出的位用零填充。如果E1
具有无符号类型,则结果值为E1
×2ᴱ²
,回绕。如果E1
具有有符号类型和非负值,并且E1
×2ᴱ²
可在结果类型中表示,则这就是结果值;否则,行为未定义。
因此,如果您的平台long long
可以表示 2⁶³,那就没问题。由于必须使用一位来指示其符号,这意味着如果有 64 位,1LL << 63
则是 UB 。long long
(long long
不能小于 64 位,但可以更宽)。
3
-
那么,答案是什么? (1 << 31) 绝对是未定义的行为,无论在哪个平台上编译。根据 C 标准,long long 至少包含 64 位,而 int 至少包含 32 位。引用听起来含糊不清。 (1 << 31)“可在结果类型中表示”,但结果在不同平台上的行为是未定义的行为 🙂
– -
3否,如果可表示(例如,如果目标的类型为 33 位或更多位宽),
1 << 31
则为定义的行为。同样,只有当您的类型为 65 位或更多位时,才是定义的行为。2³¹
int
1LL << 63
long long
– -
2@lollol:回复“(1 << 31)绝对是未定义的行为,无论它在哪个平台上编译”:这是错误的。如果超过 32 位
1 << 31
则定义。int
–
|
有几件事需要考虑(C17 6.5.7):
对每个操作数执行整数提升。结果的类型是提升后的左操作数的类型。如果右操作数的值为负数或大于或等于提升后的左操作数的宽度,则行为未定义。
E1 E2 的结果
<<
是 E1 左移 E2 位;空出的位用零填充。如果 E1 具有无符号类型,则结果值为 E1 × 2 E2,以结果类型可表示的最大值多 1 为模数。如果 E1 具有有符号类型和非负值,并且 E1 × 2 E2可在结果类型中表示,则这就是结果值;否则,行为未定义。
-
在 6.2.6 中,有符号类型的宽度
E1
正式定义为值位加上符号位。这意味着对于类型的有符号变量intn_t
,我们最多可以移动。如果我们移动的次数超过这个数字,无论是否包含负值,E1 << n
我们都会调用未定义的行为 。E1
因此,从有符号类型中“移出”数据是无效的;此代码可能有 UB:
ms_byte_cleared = (my_signed32 << 40) >> 8;
-
如果
E1
保留负值,则左移总是会引发未定义的行为。 -
如果移位导致的值大于提升的操作数所能容纳的值(本质上如果我们最终“将数据移位到符号位”),那么这也是未定义的行为。
long long
但是 C 标准并未定义a 的宽度。我们只知道它至少足够大以容纳值+9223372036854775807
(2 63 -1)(定义在 C17 5.2.4.2.1 中)。因此,我们不能严格地说它(1LL << 63)
肯定是 UB。但是我们可以说它(int64_t)1 << 63
是 UB,因为它得到的值是 2 63,大于 an 的最大值int64_t
2 63 -1。
|
(1LL << 62) 将得出4611686018427387904d
或100000000000000000000000000000000000000000000000000000000000000b
。这是 63 位。
(1LL << 63) 将得出-9223372036854775808d
或-1000000000000000000000000000000000000000000000000000000000000000n
。这是二进制补码表示,是 64 位有符号整数可表示的最小负值。这是 64 位。
(1LL << 64) 将给出0d
或0b
,这将被视为未定义的行为。
2
-
2您描述的是可能的行为,但标准明确指出,在使用 64 位二进制补码的目标上,这是未定义的- 请参阅我的答案中的第 6.5.7 节。
– -
我明白了。顺便说一下,这本书不错。
–
|
–
–
1
无论有符号变量的大小是多少,将 a 左移到 MSB(符号位)都是未定义的行为–
–
–
|