gcc
按位Leftshift(<<
)奇怪的行为。这是我的代码:
#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);
}
如果我将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
这个结果是我从compile online site
得到的如果将0传递给它,如何使foo函数输出0? (目前输出1代替)
答案 0 :(得分:2)
移动一个等于或大于左操作数的提升类型宽度的值是未定义的行为,因此您必须专门测试并避免这种情况。此外,导致溢出的带符号类型的左移也是未定义的行为,因此您还需要避免移位31:
printf("1<<(32-n):%d\n", (n > 1 && n < 33) ? 1 << (32-n) : 0);
此特定表达式对于未定义的情况使用0,但如果需要,可以以不同方式处理。
答案 1 :(得分:1)
由于您正在移位32位整数,因此移位32位将导致零值。但是,CPU的位移操作只能移位0到31位,因为其他任何东西通常都没用,只会使计算复杂化。
第一个示例1<<32
似乎有效的原因是编译器在编译时将其优化为0
,同时还打印警告。另一个例子,1<<(32-n)
具有在编译时无法确定的移位值(因此也没有警告)。相反,CPU使用减法32 - n == 32
的结果进行移位操作,但CPU只取五个最低位,因此溢出为0,结果为1 << 0 == 1
。
要解决此问题,您必须使用特殊情况n == 0
,使用更宽的数据类型,或者只使用更少的位。
答案 2 :(得分: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
似乎第一种情况是它被优化到{{1}},这与未定义的行为一致,编译器可以做任何事情,包括似乎有用的行为。
答案 3 :(得分:0)
不要惊讶。你正在处理一个32位的int,所以当你做1&lt;&lt; 32时,你已经将该位置移到了int的末尾,并将整个事件归零。
e.g。二进制:
33222222 22211111 11111000 00000000
10987654 32109876 54321098 76543210
1: 00000000 00000000 00000000 00000001
^--- position #32
答案 4 :(得分:0)
根据C标准ISO 9899:1999第6.5.7章Bitwise shift operators
:
对每个操作数执行整数提升。类型 结果是提升左操作数的结果。如果值 右操作数为负数或大于或等于宽度 升级后的左操作数,行为未定。
奇怪的是,编译器以不同的方式处理这两个表达式。但是,由于这会导致不确定的行为,所以这不是问题。您需要做的是在评估之前检查操作数,以确保它是一个有效的表达式。
答案 5 :(得分:-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);
}