[英]Converting Uppercase to Lowercase in Assembly issue
我正在写将预设字符串从大写转换为小写。 我目前正在将地址处的内容移到8位寄存器中,然后采用一种非常草率的方式测试ASCII值以查看其是否为大写。 有没有更清洁的方法可以解决?
现在,我将从ASCII值中减去65,然后与25进行比较。由于大写字母是ASCII(十进制)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
SUB和一个无符号比较是一种仅使用一个条件分支来检查输入是否在一定范围内的好方法,而不是使用单独的比较分支>= 'A'
和<= 'Z'
。
编译器尽可能使用此技巧。 另请参阅Agner Fog的“优化程序集”指南以及x86标签Wiki中的其他链接,以获取有关编写高效asm的更多内容。
您甚至可以使用它通过一个分支来检测字母字符(小写或大写):或使用0x20将使任何大写字母变为小写,但不会使任何非字母字符变为字母。 这样做,然后使用unsigned-compare技巧检查是否在小写字母范围内。 (或以~0x20
以AND开头以清除该位,并强制使用大写字母)。 我在回答字母字符的大小写而又不理会其他字符的答案中使用了这个技巧。
是的,正如您所注意到的那样,ASCII是经过设计的,因此每个字母的大写/小写字母之间的区别只是翻转一位。 每个小写字符都设置为0x20,大写字母已清除。 通常最好使用AND / OR / XOR(相对于ADD / SUB),因为在强制一种情况下,有时您可以利用不在乎初始状态的优势。
您的代码有些奇怪: PUSH AL
甚至没有在大多数汇编器中进行汇编,因为push / pop的最小大小为16位。 保存/恢复AL也没有意义,因为在循环后恢复AL之后,您会破坏整个EAX!
此外,MOV只会覆盖其目的地,因此无需对xor bl,bl
进行xor bl,bl
。
另外,您将BL用作暂存寄存器,但这是EBX的低字节(用作指针!)
我仅使用EAX,ECX和EDX的方法如下,因此我不必保存/恢复任何寄存器。 (您的函数破坏了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
LOOP指令很慢,应避免使用 。 只是忘记它甚至存在,并使用任何方便的循环条件即可。
仅当字符更改时才进行存储才能使代码更有效,因为当在无事可做的情况下使用了一段时间没有更改的内存时,它不会弄脏缓存。
不用ja NoCap
,您可以使用cmov进行无分支操作。 但是现在我不得不忽略我的建议,而宁愿使用AND / OR而不是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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.