【问题标题】:ASMx86: How to pick out a letter in a string?ASMx86:如何在字符串中挑选出一个字母?
【发布时间】:2019-09-19 09:39:06
【问题描述】:

我对字符在汇编中的工作方式感到困惑。既然一切都是位,那么这是否意味着所有字符在寄存器中基本上只是十六进制版本/ascii 值?

例如,如果我想在寄存器中输入“我喜欢派”,它会显示为“48h 4C484B45h 504945h”吗?如果我想得到所有的“i”字母,我需要使用像

这样的命令
;   "command to get string input"  
getI:
    cmp BYTE PTR [edx],48h 
    je L1
L1: 
    add [edx],1
    loop getI  

我基本上是想找到一种方法来将字符与输入的字符串隔离开来。

【问题讨论】:

  • 注意,空格也是字符。在 ASCII 编码中,它们具有 ASCII 码 32 (0x20)。此外,您通常永远不会将实际字符串 contents 放在寄存器中。在您的示例中,它肯定行不通,因为 "I like pie" 是 10 个字符(即 10 个字节),太大而无法放入寄存器(如果我们打折 SSE/AVX 寄存器)。因此,您所做的是将字符串数据放在数据部分中,或者如果它是动态分配的字符串,则放在堆或堆栈中,并且您放置在寄存器中的是该字符串数据的地址
  • 另外,ASCII 中的 48h 是 'H''i' 是 69 小时,'I' 是 49 小时。

标签: assembly x86 irvine32


【解决方案1】:

您的方法接近成功:

getI:
    cmp BYTE PTR [edx],'i'  ; your assembler will know what that is
    jne L1                  ; skip the code for 'i' handling, if it's NOT an 'i'
    ; here you do what
    ; ever you want to 
    ; with your 'i's here
L1: 
    add edx,1               ; better: 'inc edx'
    loop getI               ; loop only works, if you have CX loaded with the length of your "string"
                            ; either use it, or check for 0 chars INSIDE the loop

您的 CPU 不关心您的“字符串”,它只是内存中的字节数。
您的“我喜欢派”是字节“69h,20h,6ch,69h,6bh,65h,20h,70h,69h,65h,0”的 ascii 表示 当您将其中一个加载到寄存器中时,它只是一个字节值......字符串或字符没有什么特别的

顺便说一句:并非所有寄存器都是平等的,有些寄存器有特殊用途。您选择 edx 作为您的“字符串指针”...... x86 CPU 具有索引寄存器和使用它们的专门指令,这会更好地为您服务。但这是一个不同的故事;-)

【讨论】:

  • 非常感谢,我现在理解得更好了,但是对于“add [edx],1”,它应该是“add edx,1”。那么,如果它只是一个单引号,它指的是 ASCII 表吗?假设我放了一个 '1' 而不是 'i' 它会寻找 1 的 ASCII 版本而不是数字吗?
  • 你在 edx 上是对的,我会修复它 :) (没有检查我从你那里复制的内容 :P )再次:你的 CPU 不关心 ascii。因此,如果您想检查 ascii '1',请使用 cmp '1' ,检查二进制 1 是 cmp 1
【解决方案2】:

这是否意味着所有字符在寄存器中时基本上只是十六进制版本/ascii 值

是的,就像在 C 中一样,字符串只是 char 元素的数组。 char 只是一个窄整数类型,如 uint8_t。 (C 标准没有定义 char 是否有符号,所以如果您实际使用 C 并且想要无符号变量,请使用 uint8_t。)

无论如何,是的,在 asm 中,您应该将 ASCII 字符串中的字符视为 8 位整数,其中the value is their ASCII encoding。例如如果ax 的值来自[0..9],您可以使用以下命令将其转换为十进制数字:

add   al, '0'    ; '0' is a nice way to write 0x30

将较大的整数转换为多位数的十进制字符串显然更复杂,需要除以或乘以 10。


如果寄存器中有字母 ASCII 字符,可以将其强制为小写、大写或翻转

or    al, 0x20    ; tolower
and   al, ~0x20   ; toupper
xor   al, 0x20    ; opposite

'a' - 'A' 是 0x20,范围不跨越编码空间内的 0x20 边界。 (即所有小写字母都设置了该位,而大写字母都没有)。要检查 ascii 字符是否为字母,请参阅 my answer on a question about case-flipping for letters only


如果我把“我喜欢馅饼”放在寄存器里……

在一个寄存器中一次处理多个字符很棘手,但对于高性能来说是必不可少的。编写只保留指向字符串中某个位置的指针并一次处理一个字节的代码会更容易。

NASM 和 MASM 有 syntax for multi-character constants,所以你可以做类似的事情

mov   eax, 'abcd'    ; put those bytes into eax, like if you'd loaded from  db 'a', 'b', 'c', 'd'

除非您在汇编时知道字符串长度,否则您将不知道保存整个字符串需要多少个寄存器。通常,您一次将 4 或 8 个字符加载到寄存器中,并使用一些 SWAR bithack 来执行 check if any of the bytes are zero 之类的操作,例如,如果您正在实现 strlen()

如果你知道所有的字符都是 ASCII 字母,你可以强制它们全部小写

mov  eax, [rsi]
or   eax, 0x20202020    ; packed tolower on all 4 chars

你的循环

您正在尝试实现strchr(const char *s, int c)

getI:
    cmp BYTE PTR [edx],48h      ; so far so good
    je L1                       ; goes to L1 whether the branch is taken or not
L1: 
    add [edx],1                 ; increments the data pointed to by edx, not edx itself.
    loop getI                   ; If ECX is the string length, this is ok.  also loop is slow, don't use

另外,add [edx],1 不会汇编,因为操作数大小不明确。可以是add byte [edx], 1、word 或add dword [edx], 1。你的意思可能是inc edx

如果您知道该字符存在,则无需检查长度(或 C 样式隐式长度字符串中的终止零字节)。

例如

unsafe_strchr:
   ; ... get the char you want in al, and the pointer in esi
.search:
    inc    esi
    cmp    byte [esi-1], al
    jne  .search
; esi holds to the location

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-29
    • 1970-01-01
    • 2015-10-03
    • 1970-01-01
    • 1970-01-01
    • 2014-11-26
    相关资源
    最近更新 更多