25.1.1 MSVC 2012 x86 /Ox

清单25.1: MSVC 2012 x86 /Ox

  1. $SG4228 DB Enter temperature in Fahrenheit:’, 0aH, 00H
  2. $SG4230 DB ’%d’, 00H
  3. $SG4231 DB Error while parsing your input’, 0aH, 00H
  4. $SG4233 DB Error: incorrect temperature!’, 0aH, 00H
  5. $SG4234 DB Celsius: %d’, 0aH, 00H
  6. _fahr$ = -4 ; size = 4
  7. _main PROC
  8. push ecx
  9. push esi
  10. mov esi, DWORD PTR __imp__printf
  11. push OFFSET $SG4228 ; Enter temperature in Fahrenheit:’
  12. call esi ; call printf()
  13. lea eax, DWORD PTR _fahr$[esp+12]
  14. push eax
  15. push OFFSET $SG4230 ; ’%d
  16. call DWORD PTR __imp__scanf
  17. add esp, 12 ; 0000000cH
  18. cmp eax, 1
  19. je SHORT $LN2@main
  20. push OFFSET $SG4231 ; Error while parsing your input
  21. call esi ; call printf()
  22. add esp, 4
  23. push 0
  24. call DWORD PTR __imp__exit
  25. $LN9@main:
  26. $LN2@main:
  27. mov eax, DWORD PTR _fahr$[esp+8]
  28. add eax, -32 ; ffffffe0H
  29. lea ecx, DWORD PTR [eax+eax*4]
  30. mov eax, 954437177 ; 38e38e39H
  31. imul ecx
  32. sar edx, 1
  33. mov eax, edx
  34. shr eax, 31 ; 0000001fH
  35. add eax, edx
  36. cmp eax, -273 ; fffffeefH
  37. jge SHORT $LN1@main
  38. push OFFSET $SG4233 ; Error: incorrect temperature!’
  39. call esi ; call printf()
  40. add esp, 4
  41. push 0
  42. call DWORD PTR __imp__exit
  43. $LN10@main:
  44. $LN1@main:
  45. push eax
  46. push OFFSET $SG4234 ; Celsius: %d
  47. call esi ; call printf()
  48. add esp, 8
  49. ; return 0 - at least by C99 standard
  50. xor eax, eax
  51. pop esi
  52. pop ecx
  53. ret 0
  54. $LN8@main:
  55. _main ENDP

关于这个我们可以说的是:

  • printf()的地址先被载入了ESI寄存器中,所以printf()调用的序列会被CALL ESI处理,这是一个非常著名的编译器技术,当代码中存在多个序列调用同一个函数的时候,并且/或者有空闲的寄存器可以用上的时候,编译器就会这么做。
  • 我们知道ADD EAX,-32指令会把EAX中的数据减去32。 EAX = EAX + (-32)等同于 EAX = EAX - 32,因此编译器决定用ADD而不是用SUB,也许这样性能比较高吧。
  • LEA指令在值应当乘以5的时候用到了: lea ecx, DWORD PTR [eax+eax*4]。 是的,i + i * 4是等同于i*5的,而且LEA比IMUL运行的要快。 还有,SHL EAX,2/ ADD EAX,EAX指令对也可以替换这句,而且有些编译器就是会这么优化。
  • 用乘法做除法的技巧也会在这儿用上。
  • 虽然我们没有指定,但是main()函数依然会返回0。C99规范告诉我们[15章, 5.1.2.2.3] main()将在没有return时也会照常返回0。 这个规则仅仅对main()函数有效。 虽然MSVC并不支持C99,但是这么看说不好他还是做到了一部分呢?

25.1.2 MSVC 2012 x64 /Ox

生成的代码几乎一样,但是我发现每个exit()调用之后都有INT 3。

  1. xor ecx, ecx
  2. call QWORD PTR __imp_exit
  3. int 3

INT 3是一个调试器断点。 可以知道的是exit()是永远不会return的函数之一。所以如果他“返回”了,那么估计发生了什么奇怪的事情,也是时候启动调试器了。