分类: C/C++
2013-04-24 09:05:53
http://www.cnblogs.com/jthou/articles/713740.html
可以试试这段代码:
int main(int argc, char* argv[])
{
int i, j;
i = 0x0FFF;
j = i>>32;
return 0;
}
你会发现j仍然等于0x0FFF,而不是期望中的0。
在编译的时候,编译器会提示(在vc6和gcc4中都一样):“shift count is too large”
在这个程序中到底发生了什么事情呢?我们来看一看这段代码的汇编代码(Debug):
mov WORD PTR [ebp-8], 4095 ;11.3
$LN3:
movzx eax, WORD PTR [ebp-8] ;12.8
movsx eax, ax ;12.8
通过这段代码我们可以看到,编译器直接把代码编译成了赋值操作,而没有做移位操作.
再看一下Release版:
mov eax, 4095 ;11.2
这下更简单,直接给eax赋值了。
再看一下下面这个例子:
int main(int argc, char* argv[])
{
short i, j;
for(int k=0; k<=32; k++)
{
i = 0x0FFF;
j = i>>k;
printf("%d %d\n", i, j);
}
return 0;
}
这时候再看一下Release版的汇编代码:
.B1.2: ; Preds .B1.3 .B1.1
mov ecx, ebx ;12.11
mov eax, 4095 ;12.11
sar eax, cl ;12.11
movsx edx, ax ;12.11
push edx ;13.24
push 4095 ;13.24
push OFFSET FLAT: ??_C@_06A@?$CFd?5?$CFd?6?$AA@ ;13.24
call _printf ;13.4
; LOE ebx esi edi
.B1.7: ; Preds .B1.2
add esp, 12 ;13.4
; LOE ebx esi edi
.B1.3: ; Preds .B1.7
add ebx, 1 ;9.22
cmp ebx, 32 ;9.2
jle .B1.2 ; Prob 96% ;9.2
就会发现,编译器使用了sar指令。
如果把前面例子中的printf去掉,也就是:
int main(int argc, char* argv[])
{
short i, j;
for(int k=0; k<=32; k++)
{
i = 0x0FFF;
j = i>>k;
}
return 0;
}
这个时候,在gcc中不能编译,提示说有"unused variable",而在vc中可以顺利地编译,但我们看它生成的Rlease版汇编的时候就会发现,这两个变量运算的结果,编译器已经得知没有地方使用,就不再编译到binary code中了。
另外,我们会发现,不论在那种情况中,i>>32都等于i.
根据上述实验,我们可以得出这样的结论:
1.编译器能很好的找到程序中常量运算,然后用最简单的方式去替换它,比如前面的mov eax,4095
2.对于函数中的死代码,编译器也能很好的察觉,这些代码不会造成程序性能的下降和code size的增大。
3.这个应该是个常识,但很多C语言的书上都未提及,在C99中,对右移有这样的规定:
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.
也就是说,对于右移大于或等于位宽的操作,或者右移负数的操作,其结果将依赖于编译器的处理和硬件指令的处理,并不唯一。
我们可以试试这个例子:
int main(int argc, char* argv[])
{
short i, j;
for(int k=-2; k<=35; k++)
{
i = 0x0FFF;
j = i>>(32-k);
printf("%d %d\n", i, j);
}
return 0;
}
在X86上运行,当移负数位的时候,结果是0,当移大于等于32位的时候,结果同shift&31
对于上面的例子,比较保守的写法可以是:
int main(int argc, char* argv[])
{
int i, j;
for(int k=-1; k<=33; k++)
{
i = 0x0FFF;
int shift = 32-k;
j = shift<0?0:(shift>=32?0:(i>>shift));
}
return 0;
}
在 JVM 中,>> 操作,后面的数是 int
类型的。右移32位:
如果前一个操作数是 int 类型的话,取右移位数的低 5 位,相当于右移位数与 0x1f 做了 &
运算;
如果前一个操作数是 long 类型的话,取右移位数的低 6 位,相当于右移位数与 0x3f 做了 &
运算。
正如楼主的例子,>> 32,32(100000),可以看到它的低 5
位全是“0”,与 >> 0 的结果一致。
可见这取决于编译器是如何处理的。