【问题标题】:How can I check if a character is a letter in assembly?如何检查字符是否是汇编中的字母?
【发布时间】:2015-10-27 18:04:17
【问题描述】:

所以,我有一个代码块设置边界来检查字符是否是字母(不是数字,不是符号),但我认为它不适用于大小写之间的字符。你能帮我吗?谢谢!

mov al, byte ptr[esi + ecx]; move the first character to al
cmp al, 0                  ; compare al with null which is the end of string
je done                    ; if yes, jump to done
cmp al, 0x41               ; compare al with "A" (upper bounder)
jl next_char               ; jump to next character if less
cmp al, 0x7A               ; compare al with "z" (lower bounder)
jg next_char               ; jump to next character if greater
//do something if it's a letter
next_char:
//do something different

【问题讨论】:

  • 你的问题不清楚
  • @Amit,我已经编辑了我的问题。
  • 您是否在调试器中单步执行过它?请参阅stackoverflow.com/tags/x86/info 了解有关开始调试的一些内容以及其他链接。在 asm 中检查这个条件与在 C 中检查它没有什么不同。
  • 是的,但显然我的评论也不清楚。您的代码引用了 2 个字符串,看起来您正在尝试比较某些东西(这 2 个字符串?),并且在某些地方也有条件地“跳跃”,但完整的逻辑尚不清楚。
  • @Amit 抱歉,我删除了那行。

标签: assembly x86 ascii


【解决方案1】:

你可以或 0x20 到每个字符;这将使大写字母变为小写(并用其他非字母字符替换非字母字符):

...
je done       ; This is your existing code
or al, 0x20   ; <-- This line is new!
cmp al, 0x41  ; This is your existing code again
...

注意:如果您的代码应该使用 0x7F 以上的字母(如“Ä”、“Ó”、“Ñ”),它会变得非常复杂。这种情况下的一个问题是这些字符的 ASCII 代码在 Windows 控制台程序(例如:“Ä”= 0x8E)和 Windows GUI 程序(“Ä”= 0xC4)中不同,甚至在其他操作系统中可能不同。 ..

【讨论】:

  • 这是一个很酷的技巧,但你错过了一个地方;-)。在or 之后,比较应该是针对 0x61。
  • @Amit: 或者写成'a'。或者,使用and al, ~0x20 转换为大写。不幸的是,在je done 之前,没有人可以将比较零替换为标志设置操作。 and 会将空格 (' ') 转换为 0,而 or 的结果永远不会为零。
【解决方案2】:

您需要有一个组合多个条件的逻辑,类似于“C”语句:if((c &gt;= 'A' &amp;&amp; c &lt;= 'Z') || (c &gt;= 'a' &amp;&amp; c &lt;= 'z'))

你可以这样做:

...
je done                    ; if yes, jump to done
cmp al, 0x41               ; compare al with "A"
jl next_char               ; jump to next character if less
cmp al, 0x5A               ; compare al with "Z"
jle found_letter           ; if al is >= "A" && <= "Z" -> found a letter
cmp al, 0x61               ; compare al with "a"
jl next_char               ; jump to next character if less (since it's between "Z" & "a")
cmp al, 0x7A               ; compare al with "z"
jg next_char               ; above "Z" -> not a character
found_letter:
// ...
next_char:
// ...

【讨论】:

  • 这种将 C 直接转换为 asm 的幼稚直接翻译是正确的,但效率低下。可能有 4 条指令,其中只有 1 条是分支;看我的回答。
【解决方案3】:

正确,'Z''a' 之间有几个非字母字符的间隔。

最有效的方法是set the lower-case bit with an OR,然后使用range-check trick of sub + unsigned compare。这当然只适用于 ASCII,不适用于存在其他字母字符范围的扩展字符集。请注意,如果原始字符不是大写字符,or al, 0x20 永远不能创建小写字符,因为范围相对于 ASCII 码的 mod 32 边界“对齐”相同。

用底部的条件分支排列你的循环结构。要么使用jmp 进入循环以进行该负载和测试,要么剥离第一次迭代的那部分。 (Why are loops always compiled into "do...while" style (tail jump)?)

使用movzx 加载以避免在写入 AL 时将低字节合并到 EAX 中的错误依赖。

 ; ESI = pointer to the string
    xor    ecx, ecx            ; index = 0
    movzx  eax, byte ptr[esi]  ; test first character
    test   eax, eax
    jz    .done                ; skip the loop on empty string
 ; alternative: jmp .next_char to enter the loop
.loop:                         ; do{
    inc    ecx

    mov    edx, eax               ; save a copy of the original if needed
;;;; THESE 4 INSTRUCTIONS ARE THE ALPHA / NON-ALPHA TEST
    or     al, 0x20               ; force lowercase
    sub    al, 'a'                ; AL = 0..25 if alphabetic
    cmp    al, 'z'-'a'
    ja    .non_alphabetic         ; unsigned compare rejects too high or too low (wrapping)

;; do something if it's a letter
    jmp   .next_char
.non_alphabetic:
;; do something different, then fall through

.next_char:
    movzx  eax, byte ptr[esi + ecx]
    test   eax, eax
    jnz    .loop                 ; }while((AL = str[i]) != 0);

.done:

如果输入在 'a' 之前,sub al, 'a' 将被签名为负数,或者因为 unsigned 将包装成一个高值,所以 cmp al, 'z'-'a' / ja 将拒绝它。

如果输入在'z' 之后,sub al, 'a' 将留下一个大于 25 ('z'-'a') 的值,因此无符号比较也会拒绝它。

编译器在编译像 c &lt;= 'z' &amp;&amp; c &gt;= 'a' 这样的 C 表达式时使用这种无符号比较技巧,因此您可以确保它对于每个可能的输入都与该表达式相同。

其他样式说明:通常您只需增加 ESI,而不是同时拥有指针和索引。此外,如果您可以使用 AL 值(字母表中的索引),您可能不需要mov edx, eax。制作副本并使用这种“破坏性”测试通常比 2 个单独的分支更好。


NASM syntax allows character constants like C,所以你可以写as 'A', or 0x7Aas'z'. e.g. cmp al, 'a'`。那么你甚至不需要注释该行。

这样写(在循环顶部带有next_char 标签)在底部保存jmp。循环中的指令更少=更好。这些天写 asm 的唯一目的是性能,所以从一开始就学习这样的好技术是有意义的,如果它不是太令人困惑的话。如果没有指向 http://agner.org/optimize/ 的链接,任何汇编答案都是不完整的。

ascii(1)http://www.asciitable.com/ 的输出

Dec Hex    Dec Hex    Dec Hex  Dec Hex  Dec Hex  Dec Hex   Dec Hex   Dec Hex  
  0 00 NUL  16 10 DLE  32 20    48 30 0  64 40 @  80 50 P   96 60 `  112 70 p
  1 01 SOH  17 11 DC1  33 21 !  49 31 1  65 41 A  81 51 Q   97 61 a  113 71 q
  2 02 STX  18 12 DC2  34 22 "  50 32 2  66 42 B  82 52 R   98 62 b  114 72 r
  3 03 ETX  19 13 DC3  35 23 #  51 33 3  67 43 C  83 53 S   99 63 c  115 73 s
  4 04 EOT  20 14 DC4  36 24 $  52 34 4  68 44 D  84 54 T  100 64 d  116 74 t
  5 05 ENQ  21 15 NAK  37 25 %  53 35 5  69 45 E  85 55 U  101 65 e  117 75 u
  6 06 ACK  22 16 SYN  38 26 &  54 36 6  70 46 F  86 56 V  102 66 f  118 76 v
  7 07 BEL  23 17 ETB  39 27 '  55 37 7  71 47 G  87 57 W  103 67 g  119 77 w
  8 08 BS   24 18 CAN  40 28 (  56 38 8  72 48 H  88 58 X  104 68 h  120 78 x
  9 09 HT   25 19 EM   41 29 )  57 39 9  73 49 I  89 59 Y  105 69 i  121 79 y
 10 0A LF   26 1A SUB  42 2A *  58 3A :  74 4A J  90 5A Z  106 6A j  122 7A z
 11 0B VT   27 1B ESC  43 2B +  59 3B ;  75 4B K  91 5B [  107 6B k  123 7B {
 12 0C FF   28 1C FS   44 2C ,  60 3C <  76 4C L  92 5C \  108 6C l  124 7C |
 13 0D CR   29 1D GS   45 2D -  61 3D =  77 4D M  93 5D ]  109 6D m  125 7D }
 14 0E SO   30 1E RS   46 2E .  62 3E >  78 4E N  94 5E ^  110 6E n  126 7E ~
 15 0F SI   31 1F US   47 2F /  63 3F ?  79 4F O  95 5F _  111 6F o  127 7F DEL

【讨论】:

  • 谢谢!我会编辑它。那么,您知道如何检查“A”和“z”之间的字符吗?我尝试设置 4 个边界,但我认为它不起作用。
  • @conchimnon:用更有用的东西更新了我的答案。
  • @conchimnon:一个很好的起点是 C 编译器输出一个 C 函数,它可以满足你的需求。直接在 ASM 中编写工作量很大;在开始优化 asm 代码之前使用一个不错的起点可以节省时间。 (或者有时您会看到编译器的效率很低。)请参阅agner.org/optimize 了解有关使用 asm 的更多建议。
【解决方案4】:

此函数接受一个字符串,并使用 ascii 表值来确定它是大写字符还是小写字符。 CMP-->BLS 和 CMP-->BLI 指令确定它是大写还是小写字符。如果是小写字符,后面的代码会将字符大写。

__asm void my_capitalize(char *str)
{
cap_loop
        LDRB r1, [r0] ; Load byte into r1 from memory pointed to by r0 (str pointer)
        CMP r1, #'a'-1 ; compare it with the character before 'a'
        BLS cap_skip ; If byte is lower or same, then skip this byte
        CMP r1, #'z' ; Compare it with the 'z' character
        BHI cap_skip ; If it is higher, then skip this byte
        SUBS r1,#32 ; Else subtract out difference to capitalize it
        STRB r1, [r0] ; Store the capitalized byte back in memory
cap_skip
        ADDS r0, r0, #1 ; Increment str pointer
        CMP r1, #0 ; Was the byte 0?
        BNE cap_loop ; If not, repeat the loop
        BX lr ; Else return from subroutine
}

【讨论】:

  • 问题被标记为x86 这不是。
  • 此代码仅检查小写字母。它不会检测源字符串中的大写字母字符。我刚刚用有效的范围检查技巧更新了我的答案。
猜你喜欢
  • 1970-01-01
  • 2013-04-01
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
  • 2012-04-23
  • 2011-07-07
  • 2014-01-01
  • 1970-01-01
相关资源
最近更新 更多