【问题标题】:Converting Uppercase to Lowercase in Assembly issue在汇编问题中将大写字母转换为小写字母
【发布时间】:2016-11-01 18:11:13
【问题描述】:

我正在写将预设字符串从大写转换为小写。我目前正在将地址中的内容移动到一个 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

【问题讨论】:

  • 0x20 添加到字符中。如果0x61 &lt;= char &lt;= 0x7a,则存储。否则移动到下一个字符
  • 这段代码无效,它会在第一个字符后覆盖其他一些内存。您是否在调试器中检查过它,它在做什么?有“??????” xor bl,bl 之后的评论看起来并不乐观。您还可以使用sub bl,'A' 而不是“sub bl,65”(或更改编译器,如果它不适用于字符文字)......所以在添加 0x20 后,您可以测试“如果低于'a' 跳过写入”和“如果高于'z' 跳过写入”否则“将新值写回字符串”。无需将这些 ASCII 字符作为数字写入源代码。
  • @Ped7g:用一个分支检查范围的两侧(使用 sub/cmp/unsigned-jcc),实际上是一种很好的技术,并且效果很好。
  • @PeterCordes:是的,这不是我评论的重点,我什至建议他如何重写sub 以使其更具可读性。尽管 OP 代码的其他问题表明他最好坚持简单的“英语”流控制,并进行双方范围测试。我永远不知道如何回答那些认为blebx 是独立寄存器的人——除非在我的每个答案中写下几乎完整的教程。因此,我通常只是在评论中询问一些内容,然后等待 OP 是否进行了足够的沟通(值得付出努力,并集中精力)。但是 OP 在“第 0 课”ATM 上。

标签: assembly x86 masm lowercase


【解决方案1】:

SUB 然后进行无符号比较是一种仅使用一个条件分支检查输入是否在特定范围内的好方法,而不是为&gt;= 'A'&lt;= 'Z' 分别进行比较和分支。

编译器尽可能使用这个技巧。另请参阅 Agner Fog's Optimizing Assembly guide 标签 wiki 中的其他链接,了解有关编写高效 asm 的更多内容。

您甚至可以使用它来检测带有一个分支的字母字符(小写或大写):OR 与 0x20 将使任何大写字符变为小写,但不会使任何非字母字符变为字母。这样做,然后使用无符号比较技巧来检查是否在小写范围内。 (或以 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,因此我不必保存/恢复任何寄存器。 (您的函数破坏了 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 无分支地执行此操作。但是现在我不得不忽略我更喜欢 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-04
    • 2015-07-28
    • 1970-01-01
    • 1970-01-01
    • 2015-08-07
    • 1970-01-01
    • 2013-07-26
    • 1970-01-01
    相关资源
    最近更新 更多