2

gcc bitwise Leftshift (<<) strange behavior. Here is my code:

#include <stdio.h>
#include <string.h>

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

int main(){
    foo(0);
}

If I pass 0 as parameter, the result could be different. Compiling the source code:

$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]

Executing the program:

$demo

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

This result is what I've got from compile online site

How can I make the foo function output 0 if I pass 0 to it? (currently it outputs 1 instead)

4

6 回答 6

2

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

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

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

于 2013-06-02T03:04:51.583 回答
1

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

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

要解决此问题,您将不得不使用特殊情况n == 0、使用更广泛的数据类型或仅使用更少的位。

于 2013-06-02T03:00:49.583 回答
0

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

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

您的班次必须小于类型的大小,否则它是未定义的行为。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为与未定义行为一致的第一种情况,编译器可以做任何事情,包括看起来有效的行为。

于 2013-06-02T02:43:18.627 回答
0

不要惊讶。你正在处理一个 32 位的 int,所以当你做 1<<32 时,你已经将那个 set 位从 int 的末尾移开了,并将整个事情归零。

例如二进制:

    33222222 22211111 11111000 00000000
    10987654 32109876 54321098 76543210
 1: 00000000 00000000 00000000 00000001

   ^--- position #32
于 2013-06-02T02:43:40.807 回答
0

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

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

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

于 2013-06-02T03:18:12.667 回答
-1

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

#include <stdio.h>
#include <string.h>

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);
}
于 2013-06-02T16:33:05.493 回答