【老刘谈算法004】拿目标地址传送指令进行快速运算的骚操作——字符串转双字函数分析(1)

版权声明:本文为老刘的原创文章,转载需注明出处。 https://blog.csdn.net/qq_23693343/article/details/88096569

快速运算与x86内存定位公式

在编写x86汇编时,你是否有这样的苦恼?

  • 做简单的乘法运算,都需要写很多行,还要将寄存器中的数据导来导去。
  • 将寄存器放到其它寄存器中并加减一个数,都要写mov,add两条指令。

其实,借助x86灵活的内存定位方式,以及一个被“屈才”了的汇编指令,这些操作可以在一条汇编指令中实现

地址定位公式

在8086汇编中,汇编指令支持简单的“地址定位公式”:

  1. [bx]
  2. [bx+立即数]
  3. [si/di+bx+立即数]

在x86中,地址定位支持了简单的乘法,寄存器的限制也变得宽松:

  1. [立即数]
  2. [寄存器] (可以是8个(32位)通用寄存器中的任意一个)
  3. [寄存器+立即数]
  4. [寄存器1*{2,4,6,8}+寄存器2]
  5. [寄存器1*{2,4,6,8}+寄存器2+立即数]

x86的灵活度大大提升,便利了内存的存取,也给了我们可乘之机。

LEA指令

说来有趣,绝大部分文档对LEA指令作用的说明中,只是将其称作有效地址传送指令,而笔者认为更贴切的说明应是“简单加乘运算指令”。
虽然在指令的实际执行中,LEA大部分情况还是被用来获得局部变量、数组元素等的地址,但cpu并不会检测所得“地址”是否可用,所以即使得到的是无效地址,指令也可以正常执行。
LEA指令在CPU内部执行的操作,便是把地址定位公式计算出来,放到目标寄存器中,由于无效地址也不影响执行,LEA便可以计算满足地址定位公式的表达式

让我们解决刚开始提出的问题:
A.将一个寄存器(如ecx)的值放到其它寄存器(如edx)中并加一个数。
正常实现:

Mov Edx,Ecx
Add Ecx,idata

LEA实现:

Lea Ecx,[Edx+idata]

B:进行简单的加乘运算。
1)eax*=3
正常实现:

Push Eax
Add Eax,Eax
Add Eax,[Esp]
Add Esp,4

Lea实现:

Lea Eax,[Eax*2+Eax]

2)edx+=eax*8
正常实现:

Push Eax
Push Edx
Mov Edx,8
Mul Edx
Pop Edx
Add Edx,Eax
Pop Eax

Lea实现:

Lea Edx,[Eax*8+Edx]

可见,进行简单加乘运算时,用Lea更明了,简洁,效率也高些。

局限性

  • LEA指令不会按照结果改变标志位,想要根据结果进行条件跳转还需要另加判断。
  • 64位结果中的高32位会被丢弃。
  • 乘法为无符号数乘法。

其它

笔者才疏学浅,若有错误疏漏,希望各路大神不吝赐教。
实例代码(出自MASM32Lib):

; #########################################################################

  ; ---------------------------------------------------------------
  ;      本程序最初由 Tim Roberts 编写
  ;
  ; 	Alexander Yackubtchik 优化了部分代码
  ; ---------------------------------------------------------------

    .486
    .model flat, stdcall  ; 32 bit memory model
    option casemap :none  ; case sensitive
    .code

; #########################################################################

atodw proc String:DWORD

  ; ----------------------------------------
  ; 十进制转dword
  ; eax储存返回值
  ; ----------------------------------------

    push esi
    push edi

    xor eax, eax
    mov esi, [String]
    xor ecx, ecx
    xor edx, edx
    mov al, [esi]
    inc esi
    cmp al, 2D	;检测负号
    jne proceed	;不是负号就跳转
    mov al, byte ptr [esi]
    not edx	;FFFFFFFF
    inc esi
    jmp proceed

  @@: 
    sub al, 30h	;ascii->byte
    lea ecx, dword ptr [ecx+4*ecx]	;ecx*=5
	lea ecx, dword ptr [eax+2*ecx]	;ecx=ecx*2+eax
    mov al, byte ptr [esi]
    inc esi

  proceed:
    or al, al
    jne @B	;非0(没处理完)上跳
    lea eax, dword ptr [edx+ecx]
    xor eax, edx

    pop edi
    pop esi

    ret

atodw endp

; #########################################################################

end

猜你喜欢

转载自blog.csdn.net/qq_23693343/article/details/88096569