本来是个小技巧,看到 Dhuta 的<<汇编优化提示>>,俺就把以前验证的东西贴出来吧
调试时会经常看到形如
00401016 B8 67666666 mov eax, 66666667
0040101B F7E9 imul ecx
的代码,此为编译器将除法代码优化为乘法运算的例子。
下面是个测试列子,都是 最大速度的优化。
例子很简单,输入一个整数,输出其除以2.5的结果。
例1:
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
int i ;
scanf("%d",&i) ;
i = i/2.5 ; //唯一不同的地方
printf("i = %d\n",i) ;
return 0;
}
00401000 >/$ 51 push ecx ; msvcr90.__winitenv
00401001 |. 8D0424 lea eax, dword ptr [esp]
00401004 |. 50 push eax
00401005 |. 68 F4204000 push test11.004020F4 ; /format = "%d"
0040100A |. FF15 A4204000 call dword ptr [<&MSVCR90.scanf>] ; \scanf
00401010 |. DB4424 08 fild dword ptr [esp+8] ;例1:将两个数作为浮点数相除
00401014 |. DC35 00214000 fdiv qword ptr [_real]
0040101A |. E8 11080000 call test11._ftol2_sse
0040101F |. 50 push eax ; /<%d>
00401020 |. 68 F8204000 push test11.004020F8 ; |format = "i = %d",LF,""
00401025 |. 894424 10 mov dword ptr [esp+10], eax ; |
00401029 |. FF15 9C204000 call dword ptr [<&MSVCR90.printf>] ; \printf
0040102F |. 33C0 xor eax, eax
00401031 |. 83C4 14 add esp, 14
00401034 \. C3 retn
例2:
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
int i ;
scanf("%d",&i) ;
i = i*2/5 ;
printf("i = %d\n",i) ;
return 0;
}
00401000 > 51 push ecx ; msvcr90.__winitenv
00401001 8D0424 lea eax, dword ptr [esp]
00401004 50 push eax
00401005 68 F4204000 push test11.004020F4 ; ASCII "%d"
0040100A FF15 A4204000 call dword ptr [<&MSVCR90.scanf>] ; msvcr90.scanf
00401010 8B4C24 08 mov ecx, dword ptr [esp+8] ; /----------- 优化后的代码段,i = ecx = 输入值
00401014 03C9 add ecx, ecx
00401016 B8 67666666 mov eax, 66666667
0040101B F7E9 imul ecx
0040101D D1FA sar edx, 1
0040101F 8BC2 mov eax, edx
00401021 C1E8 1F shr eax, 1F
00401024 03C2 add eax, edx ; \------------
00401026 50 push eax
00401027 68 F8204000 push test11.004020F8 ; ASCII "i = %d",LF
0040102C 894424 10 mov dword ptr [esp+10], eax
00401030 FF15 9C204000 call dword ptr [<&MSVCR90.printf>] ; msvcr90.printf
00401036 33C0 xor eax, eax[LEFT][/LEFT]
00401038 83C4 14 add esp, 14
0040103B C3 retn
"66666667"这样的数字是事先计算好的值,不同的除法其值不同,这个例子中,编译器只会优化整除,所以编写代码时,需要手动优化,不能随意写成i/2.5 的格式。
"66666667"是怎么计算出来的?
对形如 x/y = z 的简单除法,有以下的转换:
x/y = z
x* (1/y) = z
x* M = z
M = 1/y = 1^32 / ((1^32)*y) = (1^32/y) >> 32
此例中,y = 2/5 = 0.4 , 66666667 = 1^32 * 0.4 。
非常饶舌,1^32 怎么来的?
估计还得从 "imul ecx"这个指令说起,对于32位处理器,imul(乘法)将 eax 上的数字乘以 ecx 上的数,结果(64位值)的高32位送入寄存器 edx,低32位送入寄存器 eax。优化后的代码只取有用的数据 edx,eax的数据被舍弃,即故意的数据溢出(结果已超过32位),就好象 4*1.3 = 5 一样,结果的小数点后的数字被自动舍弃。
为了达到这种效果,必须构造一个足够大的数(1^32),使任何一个数与其相乘,其结果都能反应到 edx中(imul指令)。
再看 1/y = 1^32/((1^32)*y) =1^32/y * 1^(-32) = (1^32/y) >> 32
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)