如何在汇编中遍历字符串

5

给定变量:

var1    db  "abcdefg", NULL

如何执行循环以遍历每个字母?在C++中,您可以在循环内使用var[x],然后每次增加x。有什么想法吗?

1个回答

5
在 C 和 C++ 中,字符串以 NUL 结尾。这意味着会添加一个 ASCII NUL 字符(0)到字符串的末尾,以便代码可以知道字符串的结尾。函数 strlen 从字符串的开头开始遍历,并持续循环,直到它遇到这个 NUL 字符为止。当它找到 NUL 时,它就知道这是字符串的结尾,并返回从开头到 NUL 的字符数作为该字符串的长度。
C/C++ 编译器会自动将字符串文字(双引号中的内容)以 NUL 结尾:
"abcdefg"

等同于以下数组:

{'a', 'b', 'c', 'd', 'e', 'f', 'g', 0}

我提到这个是因为Peter Rader在他的回答中建议了这样做,而你并没有真正理解他在说什么。然而,看起来你已经知道了这一点,因为你在汇编声明中附加了一个NUL字符到你的字符串中:
var1    db  "abcdefg", NULL

通常情况下,我们不使用标识符NULL来表示这个意思。特别是在C语言中,NULL被定义为一个空指针。我们只需使用字面值0,因此该定义将是:

var1    db  "abcdefg", 0

假设NULL在某处被定义为0,那么你的代码可能会正常工作。

所以你已经全部设置正确。现在你只需要编写循环:

    mov  edx, OFFSET var1    ; get starting address of string

Loop:
    mov  al, BYTE PTR [edx]  ; get next character
    inc  edx                 ; increment pointer
    test al, al              ; test value in AL and set flags
    jz   Finished            ; AL == 0, so exit the loop

    ; Otherwise, AL != 0, so we fell through.
    ; Here, you can do do something with the character in AL.
    ; ...

    jmp  Loop                ; keep looping

Finished:

你说你熟悉 CMP 指令。在上面的代码中,我使用了 TEST 而不是 CMP。你可以等价地编写:

cmp  al, 0

但是

test al, al

使用这种写法略微更高效,因为它是一条较短的指令,所以当我比较寄存器的值是否为0时,我习惯于以这种方式编写。编译器也会生成这段代码,所以熟悉这种写法很有好处。


额外讲述: 一种表示字符串的替代方式是将字符串的长度(以字符为单位)与字符串本身一起存储。这是传统Pascal语言所做的。这样,您就不需要在字符串末尾使用特殊的NUL哨兵字符。相反,声明将如下所示:

var1    db  7, "abcdefg"

每个字符串的第一个字节代表其长度。 这比C语言风格有各种优势,主要是你不必迭代整个字符串来确定其长度。当然,主要缺点是字符串的长度限制为255个字符,因为BYTE只能容纳这么多。
总之,由于长度事先已知,您不再检查NUL字符,而只需迭代与字符串中的字符相同的次数。
    mov  edx, OFFSET var1    ; get starting address of string
    mov  cl, BYTE PTR [edx]  ; get length of string

Loop:
    inc  edx                 ; increment pointer
    dec  cl                  ; decrement length
    mov  al, BYTE PTR [edx]  ; get next character
    jz   Finished            ; CL == 0, so exit the loop

    ; Do something with the character in AL.
    ; ...

    jmp  Loop                ; keep looping

Finished:

在上面的代码中,我假设所有字符串的长度都至少为1个字符。这可能是一个安全的假设,并避免了在循环之前进行长度检查的需要。
或者,您可以执行您提到的数组索引,但如果您想正向迭代字符串,就必须要小心。
    mov   edx, OFFSET var1        ; get starting address of string
    movzx ecx, BYTE PTR [edx]     ; get length of string
    lea   edx, [ecx+1]            ; increment pointer by 1 + number of chars
    neg   ecx                     ; negate the length counter
Loop:
    mov   al, BYTE PTR [edx+ecx]  ; get next character

    ; Do something with the character in AL.
    ; ...

    inc   ecx
    jnz   Loop                     ; CL != 0, so keep looping

基本上,我们将EDX设置为指向字符串的末尾,将计数器(ECX)设置为字符串长度的负数,然后通过索引[EDX+ECX](由于我们取反了ECX,所以等同于[EDX-ECX])来读取字符。

肯定有比我想出的更好(更聪明)的方法来做到这一点,但你应该明白了。


1
我认为问题更多地涉及如何索引数组。因此,我认为使用mov al,[var1 + edx]的简单解决方案应该是合适的。 - Ajay Brahmakshatriya
1
也许吧。它确实说了,“如何执行循环以遍历每个字母?”,所以这就是我回答的部分。当然,如果你只想获取一个字符,那么mov al, BYTE PTR [var1 + index](其中index是立即值或包含该值的寄存器)就可以工作。不确定我是否应该让答案更长。 :-) - Cody Gray
mov al,0,rep scasb? - Marco van de Voort
1
是的,rep scasb 可以用于通过搜索终止的 NUL 字符来查找字符串的长度。这是一种在汇编语言中实现 strlen 的(极其)低效的方法(除非你是为 8088 编程)。 - Cody Gray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接