我的代码中有一个问题,在C中按位移位,我将其归结为以下示例:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
int main(int argc, char **argv)
{
char a = 1;
int i=0;
uint64_t b;
for(i=0; i<64; i++)
{
b=0;
printf("i=%d\n", i);
b = (a<< ((uint64_t) i));
printf("%" PRIu64 "\n", b);
}
return 0;
}
由于MWE a
中不明显的原因char
是a
,我希望从中产生2到2 ^ 63的幂。它失败了,因为有些奇怪的事情正在发生,因为uint64_t
不是b = ((uint64_t)a<< ((uint64_t) i))
。显而易见的解决方法是
i=30
1073741824
i=31
18446744071562067968
i=32
1
i=33
2
为了理解究竟发生了什么,我在MWE上面写了一个例子,我得到了输出(部分显示):
i=30
现在我想知道如何解释从i=31
到i=32
的跳转?它如何在gcc (SUSE Linux) 4.8.5
处再次成为1?
如果感兴趣,我使用gcc(ClientGlobalContext.js.aspx
)
答案 0 :(得分:3)
您执行的转移大于相关类型的大小。
首先,a
(类型为char
)被提升为int
类型。详情见C standard:
如果可以使用
int
或unsigned int
,则可以在表达式中使用以下内容:
- 具有整数类型(
int
或unsigned int
除外)的对象或表达式,其整数转换等级小于或等于 等级int
和unsigned int
。- 类型为
_Bool
,int
,signed int
或unsigned int
的位字段。如果
int
可以表示原始类型的所有值(限制为 通过宽度,对于位字段),该值被转换为int
; 否则,它将转换为unsigned int
。这些被称为 整数促销。所有其他类型的整数不变 促销。
假设系统上int
为32位,向左移动超过30位会调用未定义的行为。这在标准的第6.5.7节中有详细说明:
3 对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。 如果值 右操作数是负数还是大于或等于 提升左操作数的宽度,行为未定义
4 E1&lt;&lt; E2 是E1左移 E2 位位置;腾空 位用零填充。如果 E1 具有无符号类型,则值为 结果是 E1×2 E2 ,减去模数超过最大值 在结果类型中可表示。 如果E1有签名类型和 非负值,E1×2 E2 在结果类型中可表示, 那就是结果价值;否则,行为是 未定义。强>
答案 1 :(得分:3)
在这里开始的(有些出乎意料的)规则就是<<
,
结果的类型是提升的左操作数
的类型
这意味着a << (uint64_t) i
的类型为int
,因为char
的{{1}}类型会自动扩展为a
。
您的int
似乎是32位2的补码签名类型。因此,对于大于或等于31的int
,表达式的行为是 undefined 。
答案 2 :(得分:1)
因为这是一种未定义的行为。
换档表达:
对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负或大于或等于提升左操作数的宽度,则行为未定义。
此处char a
被提升为int
[请参阅here why]。在您的机器中,int
的宽度不大于63,因此根据标准,行为未定义。
答案 3 :(得分:1)
许多答案已经解决了您的代码具有未定义的行为。因此,对发生的事情进行推理一般都没有意义(因为任何事情都可能发生......)。
但....有时它实际上非常有趣 - 请记住,纯猜测和不保证是正确的和高度依赖系统和....
所有免责声明都已到位......
奇数18446744071562067968来自何处?
我们假设32位int
,并注意到由于整数提升,您的char a = 1;
也可能是int a = 1;
。所以我们可以写:
int a = 1;
int as = a << 31; // Undefined behavior here as 1*2^31 can't be stored in 32 bit int
uint64_t b = as;
printf("%" PRIu64 "\n", b);
输出:
18446744071562067968
嗯....我们有那个神秘的18446744071562067968但为什么呢?
让我们再做一次打印:
int a = 1;
int as = a << 31; // Undefined behavior here
uint64_t b = as;
printf("%d\n", as);
printf("%" PRIu64 "\n", b);
输出
-2147483648
18446744071562067968
所以as
是否定的,所以我们确实这样做了:
uint64_t b = -2147483648;
由于b是无符号的,因此上述计算如下:
uint64_t b = UINT64_MAX + 1 - 2147483648; // which is 18446744071562067968
所以现在我们知道18446744071562067968来自哪里。毕竟不是那么神秘。
但这留下了另一个问题 - 为什么as
为负?
int a = 1;
int as = a << 31; // Undefined behavior here
uint64_t b = as;
printf("%d\n", as);
printf("%x\n", as); // Print as in hex
printf("%" PRIu64 "\n", b);
输出:
-2147483648
80000000
18446744071562067968
因此,在十六进制as
中,80000000
实际上是1
左移31次。所以处理器只是做了我们要求的,即1 << 31
C标准没有定义它,但你的/我的处理器只是实现了这样做 - 其他机器可能做其他事情。
80000000
作为32位2的补码是-2147483648
,因此我们知道为什么as
是否定的。