在装配问题中将大写转换为小写

时间:2016-11-01 18:11:13

标签: assembly x86 masm lowercase

我正在编写将预设字符串从大写转换为小写。我现在正在将地址移动到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

1 个答案:

答案 0 :(得分:3)

SUB然后无符号比较是仅使用一个条件分支检查输入在特定范围内的好方法,而不是>= 'A'<= 'Z'的单独比较和分支。

编译器尽可能使用此技巧。另请参阅Agner Fog's Optimizing Assembly guide以及标记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