我正在编写将预设字符串从大写转换为小写。我现在正在将地址移动到8位寄存器,然后以非常粗略的方式测试ASCII值以查看它是否为大写。有更清洁的方法吗?
现在我从ASCII值中减去65并与25进行比较。由于大写是ASCII(dec)65-90,任何大写字母都将导致0-25。
.DATA
string DB "ATest This String?.,/[}", '$'
strSize DD 23
.CODE
strToLower PROC
LEA EAX, string
PUSH EAX
CALL toLower2 ; write toLower2
POP EAX
LEA EAX, string ; return char* to C++
RET
strToLower ENDP
;---------------------------------------------
;Procedure: Convert to LowerCase
;Input: Address in EBX
; unsigned in AL for each letter
;Output: EAX will contain new string
;---------------------------------------------
toLower2 PROC ;65-90 is upper, 97-122 is lower (XOR 32?)
LEA EBX, string
MOVE ECX, strSize
PUSH AL ; PUSH AL before manipulating it
loop1: MOV AL, [EBX] ; Put char into AL to manipulate
XOR BL, BL ;?????????????
MOV BL, AL ;Set condition here???
SUB BL, 65 ;?????????????
CMP BL, 25 ;if(i > 64 && < 91) i += 32;
JA NoCap ;
ADD AL, 32 ;Adds 32 to ASCII value, making lower
NoCap: MOV [EBX], AL
INC EBX
LOOP loop1
POP AL ;Replace/POP AL
LEA EAX, string
toLower2 ENDP
END
答案 0 :(得分:3)
SUB然后无符号比较是仅使用一个条件分支检查输入在特定范围内的好方法,而不是>= 'A'
和<= 'Z'
的单独比较和分支。
编译器尽可能使用此技巧。另请参阅Agner Fog's Optimizing Assembly guide以及x86标记wiki中的其他链接,了解有关编写高效asm的更多信息。
您甚至可以使用它来检测带有一个分支的字母字符(小写或大写):或者使用0x20会使任何大写字符小写,但不会使任何非字母字符按字母顺序排列。这样做,然后使用unsigned-compare技巧检查是否属于小写范围。 (或者以AND与~0x20
开始清除该位,强制大写)。我在an answer on flipping the case of alphabetic characters while leaving other characters alone中使用了这个技巧。
是的,正如您所注意到的那样,ASCII的设计使得每个字母的大写/小写区别之间的区别只是翻了一位。每个小写字符都设置为0x20,而大写字母则清除它。 AND / OR / XOR通常更适合执行此操作(与ADD / SUB相比),因为在强制执行一个案例时,您有时可以利用不关心初始状态。
你的代码有一些奇怪的东西:PUSH AL
甚至不能与大多数汇编程序组装,因为push / pop的最小大小是16位。保存/恢复AL也没有意义,因为你在循环后恢复AL后立即破坏了整个EAX!
此外,MOV只会覆盖其目的地,因此无需xor bl,bl
。
另外,你使用BL作为临时寄存器,但它是EBX的低字节(你用作指针!)
我只能使用EAX,ECX和EDX这样做,所以我不必保存/恢复任何寄存器。 (您的函数blobbers EBX,大多数32位和64位调用约定需要保存/恢复功能)。如果string
没有静态分配,我需要额外的注册,让我将其地址用作直接常量。
toLower2 PROC ;65-90 is upper, 97-122 is lower (XOR 32?)
mov edx, OFFSET string ; don't need LEA for this, and mov is slightly more efficient
add edx, strSize ; This should really be an equ definition, not a load from memory.
; edx starts at one-past-the-end, and we loop back to the start
loop1:
dec edx
movzx eax, byte [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too, but this avoids a partial-register stall when doing the mov+sub in one instruction with LEA
lea ecx, [eax - 'A'] ; cl = al-'A', and we don't care about the rest of the register
cmp cl, 25 ;if(c >= 'A' && c <= 'Z') c |= 0x20;
ja NoCap
or al, 0x20 ; tolower
mov [edx], al ; since we're branching anyway, make the store conditional
NoCap:
cmp edx, OFFSET string
ja loop1
mov eax, edx
toLower2 ENDP
The LOOP instruction is slow, and should be avoided。忘记它甚至存在并使用任何循环条件是方便的。
只有在角色更改时才进行存储会使代码更有效率,因为如果在没有任何操作的情况下暂时不改变的内存上使用它就不会弄脏缓存
而不是ja NoCap
,您可以使用cmov无分支地执行此操作。但是现在我不得不忽略我的建议而不是ADD / SUB而不是ADD / SUB,因为我们可以使用LEA来添加0x20而不影响标志,从而为我们节省了一个寄存器。
loop1:
dec edx
movzx eax, byte [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too, but this avoids a partial-register stall when doing the mov+sub in one instruction with LEA
lea ecx, [eax - 'A'] ; cl = al-'A', and we don't care about the rest of the register
cmp cl, 25 ;if(c >= 'A' && c <= 'Z') c += 0x20;
lea ecx, [eax + 0x20] ; without affecting flags
cmovna eax, ecx ; take the +0x20 version if it was in the uppercase range to start with
; al = tolower(al)
mov [edx], al
cmp edx, OFFSET string
ja loop1