了解太小类型的转移操作

时间:2018-01-31 13:05:13

标签: c bit-manipulation bit-shift

我的代码中有一个问题,在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中不明显的原因chara,我希望从中产生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=31i=32的跳转?它如何在gcc (SUSE Linux) 4.8.5处再次成为1?

如果感兴趣,我使用gcc(ClientGlobalContext.js.aspx

编译了上面的代码

4 个答案:

答案 0 :(得分:3)

您执行的转移大于相关类型的大小。

首先,a(类型为char)被提升为int类型。详情见C standard

的第6.3.1.1节
  

如果可以使用intunsigned int,则可以在表达式中使用以下内容:

     
      
  • 具有整数类型(intunsigned int除外)的对象或表达式,其整数转换等级小于或等于   等级intunsigned int
  •   
  • 类型为_Boolintsigned intunsigned 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)

因为这是一种未定义的行为。

C99

  

换档表达:

     

对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负或大于或等于提升左操作数的宽度,则行为未定义。

此处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是否定的。