以下小程序在Mac上使用GCC 4.2.1版(Apple Inc. build 5664)非常尴尬。
#include <stdio.h>
int main(){
int x = 1 << 32;
int y = 32;
int z = 1 << y;
printf("x:%d, z: %d\n", x, z);
}
结果是x:0, z: 1
。
任何想法为什么x和z的值不同?
非常感谢。
答案 0 :(得分:16)
简答:英特尔处理器将移位计数屏蔽为5位(最多31位)。换句话说,实际执行的移位是(32 | 31)= 0位(无变化)。
在Linux 32位PC上使用gcc会出现相同的结果。
我收集了这个程序的较短版本,因为我很困惑为什么32位的左移应该导致非零值:
int main(){
int y = 32;
unsigned int z = 1 << y;
unsigned int k = 1;
k <<= y;
printf("z: %u, k: %u\n", z, k);
}
..使用命令gcc -Wall -o a.s -S deleteme.c
(评论是我自己的)
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $36, %esp
movl $32, -16(%ebp) ; y = 32
movl -16(%ebp), %ecx ; 32 in CX register
movl $1, %eax ; AX = 1
sall %cl, %eax ; AX <<= 32(32)
movl %eax, -12(%ebp) ; z = AX
movl $1, -8(%ebp) ; k = 1
movl -16(%ebp), %ecx ; CX = y = 32
sall %cl, -8(%ebp) ; k <<= CX(32)
movl -8(%ebp), %eax ; AX = k
movl %eax, 8(%esp)
movl -12(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
addl $36, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
好的,这是什么意思?这条指令让我感到困惑:
sall %cl, -8(%ebp) ; k <<= CX(32)
显然k 向左移位32位。
你有我 - 它正在使用sall
指令arithmetic shift。我不知道为什么将它旋转32会导致位重新出现在初始位置。我最初的推测是处理器被优化以在一个时钟周期内执行该指令 - 这意味着任何超过31的移位都将被视为无关紧要。但我很想找到答案,因为我希望旋转应该导致所有位都从数据类型的左端掉落。
我找到了http://faydoc.tripod.com/cpu/sal.htm的链接,它解释了移位计数(在CL寄存器中)被屏蔽为5位。这意味着如果您尝试移位32位,则执行的实际移位将为零位(即无更改)。答案就是答案!
答案 1 :(得分:16)
如果ints
为32位或更短,则行为未定义... 未定义的行为无法解释。
标准说:
6.5.7 / 3 [...]如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。
您可以检查int
宽度位大小,例如:
#include <limits.h>
#include <stdio.h>
int main(void) {
printf("bits in an int: %d\n", CHAR_BIT * (int)sizeof (int));
return 0;
}
您可以检查int
宽度(可以有填充位),例如:
#include <limits.h>
#include <stdio.h>
int main(void) {
int width = 0;
int tmp = INT_MAX;
while (tmp) {
tmp >>= 1;
width++;
}
printf("width of an int: %d\n", width + 1 /* for the sign bit */);
return 0;
}
标准6.2.6.2/2:对于有符号整数类型,对象表示的位应分为三组:值位,填充位和符号位。不需要任何填充位;应该只有一个符号位
答案 2 :(得分:6)
C99标准表示将数字移位操作数的位(或更多)的宽度的结果是不确定的。为什么呢?
这样,编译器就可以为特定的体系结构创建最有效的代码。例如,i386移位指令使用5位宽的字段作为位数来移位32位操作数。 C99标准允许编译器简单地获取移位计数的后五位并将它们放入字段中。显然,这意味着32位(=二进制100000)的移位与0的移位相同,因此结果将是左操作数不变。
不同的CPU架构可能使用更宽的位字段,比如32位。编译器仍然可以将移位计数直接放在字段中,但这次结果将为0,因为32位的移位会将所有位移出左操作数。
如果C99将这些行为中的一个或另一个定义为正确,则英特尔的编译器必须对过大的移位计数进行特殊检查,否则非i386的编译器必须屏蔽移位计数。
原因
int x = 1 << 32;
和
int z = 1 << y;
给出不同的结果是因为第一次计算是一个常量表达式,完全由编译器执行。编译器必须使用64位算术计算常量表达式。第二个表达式由编译器生成的代码计算。由于y和z的类型都是int
,因此代码使用32位宽的int生成计算(i386上的int为32位,Apple上为gcc的x86_64)。
答案 3 :(得分:0)
在我看来“int x = y&lt;&lt; 32;”如果sizeof(int)== 4则没有意义。
但我遇到了类似的问题:
长y = ...... long x = y&lt;&lt; 32;
我收到警告“警告:左移计数&gt; =类型的宽度”,即使sizeof(long)在所讨论的目标上为8。我通过这样做取消了警告:
长x =(y <&lt; 16)&lt;&lt; 16;
这似乎有效。
在64位架构上没有警告。在32位架构上有。