A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© HM汪磊   /  2013-2-28 12:55  /  3551 人查看  /  25 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

孙传磊 发表于 2013-2-28 19:35
java里就是这样规定的,下面的例子说明了这一点:
public class ByteShift {
        public static void main(S ...

关于当右移的运算数是byte 和short类型时,将自动把这些类型扩大为 int 型,这个确实是java规定。但是移位不是只能是32位或者64位吧?在早期的计算机,存储字长是8位或者16位的,这些移位取模时就应该是字长的值,即8或16,只对32或64的情况有意义的话应该是在32位字长的计算机,也就是现在大多数的计算机。
回复 使用道具 举报
本帖最后由 王俊杰 于 2013-2-28 21:13 编辑

移位操作的右操作数应当严格小于左操作数位数的值。
x是int类型,32位,进行移位操作时,移动位数应当小于32,否则这种操作可能是未定义的。
回复 使用道具 举报
赵海洋 发表于 2013-2-28 14:27
这个吧,首先要明白这个原码和算术移位以及逻辑移位。
x>>>32,是指x右移32位。这个是指不带符号的算术移位 ...

负数才是以补码形式存放的  - -||
回复 使用道具 举报
陈丽莉 发表于 2013-3-1 20:02
负数才是以补码形式存放的  - -||

斑竹大人,正数的补码和原码是一样的,这个,可不可以认为正数和负数都是以补码形式存储的??既然一样,这样理解好记啊,嘿嘿。
回复 使用道具 举报
陈丽莉 发表于 2013-3-1 20:02
负数才是以补码形式存放的  - -||

姐姐 ,正数的补码不就是原码么, 同意楼上观点,统统按补码记,便于记忆、   
回复 使用道具 举报


  1. 可以试试这段代码:

  2. int main(int argc, char* argv[])

  3. {

  4. int i, j;

  5. i = 0x0FFF;

  6. j = i>>32;

  7. return 0;

  8. }

  9. 你会发现j仍然等于0x0FFF,而不是期望中的0。

  10. 在编译的时候,编译器会提示(在vc6和gcc4中都一样):“shift count is too large”

  11. 在这个程序中到底发生了什么事情呢?我们来看一看这段代码的汇编代码(Debug):

  12. mov WORD PTR [ebp-8], 4095 ;11.3

  13. $LN3:

  14. movzx eax, WORD PTR [ebp-8] ;12.8

  15. movsx eax, ax ;12.8

  16. 通过这段代码我们可以看到,编译器直接把代码编译成了赋值操作,而没有做移位操作.

  17. 再看一下Release版:

  18. mov eax, 4095 ;11.2

  19. 这下更简单,直接给eax赋值了。

  20. 再看一下下面这个例子:

  21. int main(int argc, char* argv[])

  22. {

  23. short i, j;

  24. for(int k=0; k<=32; k++)

  25. {

  26. i = 0x0FFF;

  27. j = i>>k;

  28. printf("%d %d\n", i, j);

  29. }

  30. return 0;

  31. }

  32. 这时候再看一下Release版的汇编代码:

  33. .B1.2: ; Preds .B1.3 .B1.1

  34. mov ecx, ebx ;12.11

  35. mov eax, 4095 ;12.11

  36. sar eax, cl ;12.11

  37. movsx edx, ax ;12.11

  38. push edx ;13.24

  39. push 4095 ;13.24

  40. push OFFSET FLAT: ??_C@_06A@?$CFd?5?$CFd?6?$AA@ ;13.24

  41. call _printf ;13.4

  42. ; LOE ebx esi edi

  43. .B1.7: ; Preds .B1.2

  44. add esp, 12 ;13.4

  45. ; LOE ebx esi edi

  46. .B1.3: ; Preds .B1.7

  47. add ebx, 1 ;9.22

  48. cmp ebx, 32 ;9.2

  49. jle .B1.2 ; Prob 96% ;9.2

  50. 就会发现,编译器使用了sar指令。

  51. 如果把前面例子中的printf去掉,也就是:

  52. int main(int argc, char* argv[])

  53. {

  54. short i, j;

  55. for(int k=0; k<=32; k++)

  56. {

  57. i = 0x0FFF;

  58. j = i>>k;

  59. }

  60. return 0;

  61. }

  62. 这个时候,在gcc中不能编译,提示说有"unused variable",而在vc中可以顺利地编译,但我们看它生成的Rlease版汇编的时候就会发现,这两个变量运算的结果,编译器已经得知没有地方使用,就不再编译到binary code中了。

  63. 另外,我们会发现,不论在那种情况中,i>>32都等于i.

  64. 根据上述实验,我们可以得出这样的结论:

  65. 1.编译器能很好的找到程序中常量运算,然后用最简单的方式去替换它,比如前面的mov eax,4095

  66. 2.对于函数中的死代码,编译器也能很好的察觉,这些代码不会造成程序性能的下降和code size的增大。

  67. 3.这个应该是个常识,但很多C语言的书上都未提及,在C99中,对右移有这样的规定:

  68. If the value of the right operand is negative or is

  69. greater than or equal to the width of the promoted left operand, the behavior is undefined.

  70. 也就是说,对于右移大于或等于位宽的操作,或者右移负数的操作,其结果将依赖于编译器的处理和硬件指令的处理,并不唯一。

  71. 我们可以试试这个例子:

  72. int main(int argc, char* argv[])

  73. {

  74. short i, j;

  75. for(int k=-2; k<=35; k++)

  76. {

  77. i = 0x0FFF;

  78. j = i>>(32-k);

  79. printf("%d %d\n", i, j);

  80. }

  81. return 0;

  82. }

  83. 在X86上运行,当移负数位的时候,结果是0,当移大于等于32位的时候,结果同shift&31

  84. 对于上面的例子,比较保守的写法可以是:

  85. int main(int argc, char* argv[])

  86. {

  87. int i, j;

  88. for(int k=-1; k<=33; k++)

  89. {

  90. i = 0x0FFF;

  91. int shift = 32-k;

  92. j = shift<0?0:(shift>=32?0:(i>>shift));

  93. }

  94. return 0;

  95. }





  96. 在 JVM 中,>> 操作,后面的数是 int 类型的。右移32位:

  97. 如果前一个操作数是 int 类型的话,取右移位数的低 5 位,相当于右移位数与 0x1f 做了 & 运算;
  98. 如果前一个操作数是 long 类型的话,取右移位数的低 6 位,相当于右移位数与 0x3f 做了 & 运算。

  99. 正如楼主的例子,>> 32,32(100000),可以看到它的低 5 位全是“0”,与 >> 0 的结果一致。



  100. 可见这取决于编译器是如何处理的。
复制代码
回复 使用道具 举报
12
您需要登录后才可以回帖 登录 | 加入黑马