c中的左移位

Left shift bits in c

我一直在做一些关于位操作的愚蠢测试,我发现了这个问题。我执行这段代码:

int main(){
  unsigned int i;
  for (i=1; i<34; i++)
  {
    unsigned long temp = i;
    unsigned long mul = 1;
    unsigned long val;
    unsigned long one = 1;

    // Way 1
    while (temp--)
      mul = mul << one;

    // Way 2
    val = (one<<i);

    printf(" \n 1<<%i \n mul: 0x%X , val: 0x%X\n",i, mul, val); 
  }
}

当然知道i>31的时候会产生溢出。我认为代码的两部分(way1 和 way2)应该输出相同的结果。但我明白了(最后):

 /* ... correct results from i=1 to i=31 ... */
 1<<30 
 mul: 0x40000000 , val: 0x40000000 

 1<<31 
 mul: 0x80000000 , val: 0x80000000 

 1<<32 
 mul: **0x0** , val: **0x1** 

 1<<33 
 mul: **0x0** , val: **0x2**

为什么,如果两个指令都是左移,程序会产生不同的输出?好像 way2 部分产生了一个轮班,但我不知道为什么,我真的认为 "mul" 总是得到正确的值。

我在Intel 32位机器下编译,gcc版本4.4.7

可能是因为这是未定义的行为?根据§6.5.7:

If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

如果

val = (one<<i);

i 大于或等于 32 时,行为未定义。

但是,如果

while (temp--)
   mul = mul << one;

对于大于 32 的移位,它将移位零并且结果被定义为(零)。

当我用 -Wall 编译你的代码时,我收到了投诉:

BASH> gcc -Wall left-shift.c 
left-shift.c: In function ‘main’:
left-shift.c:21:12: warning: format ‘%X’ expects argument of type ‘unsigned int’, but argument 3 has type ‘long unsigned int’ [-Wformat=]
     printf(" \n 1<<%i \n mul: 0x%X , val: 0x%X\n",i, mul, val); 
            ^
left-shift.c:21:12: warning: format ‘%X’ expects argument of type ‘unsigned int’, but argument 4 has type ‘long unsigned int’ [-Wformat=]

所以我把printf改成了

printf(" \n 1<<%i \n mul: 0x%lX , val: 0x%lX\n",i, mul, val);

通过此更改 "mul" 和 "val" 显示相同的结果:

 1<<30 
 mul: 0x40000000 , val: 0x40000000

 1<<31 
 mul: 0x80000000 , val: 0x80000000

 1<<32 
 mul: 0x100000000 , val: 0x100000000

 1<<33 
 mul: 0x200000000 , val: 0x200000000

系统信息:

BASH> gcc --version
gcc (Ubuntu 5.3.1-14ubuntu2.1) 5.3.1 20160413
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
BASH> uname -a
Linux bm-pc-ubuntu 4.4.0-24-generic #43-Ubuntu SMP Wed Jun 8 19:27:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
BASH> lsb_release --all
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04 LTS
Release:    16.04
Codename:   xenial

当你这样做时:

val = (one<<i);

您正在向左移动 i。如果 i 大于 31,则结果为 undefined behavior,这意味着结果可能是也可能不是您所期望的。

来自 C standard 的第 6.5.7.3 节:

The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

但是,当您这样做时:

while (temp--)
  mul = mul << one;

您正在左移 1 i 次。这是明确定义的,因此它为您提供了您期望的价值。

此外,您使用 %X 来打印 long,而您应该使用 %lX。这也会导致未定义的行为。