Bitwise Leftshift(<<)奇怪的行为

gcc bitwise Leftshift( << )奇怪的行为。 这是我的代码:

 #include  #include  void foo(int n){ printf("1<<32:%d\n", 1<<32); printf("1<<(32-n):%d\n", 1<<(32-n)); } int main(){ foo(0); } 

如果我将0作为参数传递,结果可能会有所不同。 编译源代码:

 $gcc main.c -o demo -lm -pthread -lgmp -lreadline 2>&1 main.c: In function 'foo': main.c:5:3: warning: left shift count >= width of type [enabled by default] 

执行程序:

 $demo 1<<32:0 1<<(32-n):1 

这个结果是我从编译在线网站得到的

如果我将0传递给它,如何使foo函数输出0? (目前输出1代替)

通过等于或大于左操作数的提升类型的宽度的值进行移位是未定义的行为,因此您必须专门测试并避免这种情况。 另外,导致溢出的带符号类型的左移也是未定义的行为,因此您还需要避免移位31:

 printf("1<<(32-n):%d\n", (n > 1 && n < 33) ? 1 << (32-n) : 0); 

对于未定义的情况,此特定表达式使用0,但如果需要,可以不同地处理这些情况。

由于您正在移位32位整数,因此移位32位将导致零值。 但是,CPU的位移操作只能移位0到31位,因为其他任何东西通常都没用,并且只会使计算复杂化。

第一个例子1<<32似乎有效的原因是编译器在编译时将其优化为0 ,同时还打印警告。 另一个例子, 1<<(32-n)具有在编译时无法确定的移位值(因此也没有警告)。 相反,CPU使用减法32 - n == 32进行移位操作,但CPU仅取五个最低位,因此溢出为0,结果为1 << 0 == 1

要解决此问题,您必须使用特殊情况n == 0 ,使用更宽的数据类型,或者只使用更少的位。

不要惊讶。 你正在处理一个32位的int,所以当你执行1 << 32时,你已经将该位置移位到int的末尾,并将整个事件归零。

例如二进制:

  33222222 22211111 11111000 00000000 10987654 32109876 54321098 76543210 1: 00000000 00000000 00000000 00000001 ^--- position #32 

gcc告诉你警告的问题是什么:

 main.c:5:3: warning: left shift count >= width of type [enabled by default] 

您的class次需要小于该类型的大小,否则它是未定义的行为。 C99标准草案第6.5.7按位移位操作员3段说:

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

为什么第一个printf与第二个不同? 如果使用-fdump-tree-original构建,您将看到为foo生成以下代码:

 printf ((const char * restrict) "1<<32:%d\n", 0); printf ((const char * restrict) "1<<(32-n):%d\n", 1 << 32 - n); 

似乎第一种情况是它被优化为0 ,这与未定义的行为一致,编译器可以做任何事情,包括似乎有用的行为

根据C标准ISO 9899:1999第6.5.7章Bitwise shift operators

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

很奇怪,编译器以不同的方式处理这两个表达式。 但是,由于这会导致不确定的行为,所以这不是问题。 您需要做的是在评估之前检查操作数,以确保它是一个有效的表达式。

我终于找到了一个解决方案,至少使输出相同。

 #include  #include  void foo(int n){ printf("1<<32:%d\n", 1<<32); printf("1<<(32-n):%d\n", (1<<(31-n))<<1); } int main(){ foo(0); }