检查用户的输入是否为回文

Checking if the user's input is a palindrome

我遇到一段代码,其中汇编程序可以检查字符串是否为回文,但字符串是硬编码的。我想练习和修改代码来做同样的事情,除了程序将接受用户输入的字符串。

我从这个post得到了主要代码:Palindrome using NASM: answer by user1157391

下面是我想出的代码,除了我不断收到错误:第 39 行:无效的操作数类型和 第 41 行:除法运算符只能应用于标量值

section .data
        msg     db "Please provide a word: ",0
        len     equ $ - msg

        msg1    db "The word is a palindrome",0
        len1    equ $ - msg1
        
        msg2    db "The word is not pallindrome",0
        len2    equ $ - msg2
        
        nline   db 0xA
        nlen    equ $ - nline
        
segment .bss
        input   resb  10        ; reserve 10 bytes for input
        length  resb  10

        section .text
        global _start

_start:
        mov     edx, len        ; number of bytes to write
        mov     ecx, msg        ; ECX will point to the address of the string msg
        mov     ebx, 1          ; write to the STDOUT file
        mov     eax, 4          ; invoke SYS_WRITE (kernel opcode 4)
        int     0x80
    
        mov     edx, 11         ; number of bytes to read
        mov     ecx, input      ; reserve space for the user's input
        mov     ebx, 0          ; write to the STDIN file
        mov     eax, 3          ; invoke SYS_READ (kernel opcode 3)
        int     0x80

        mov     eax, input
        call    strlen
        mov     [length], eax

        mov     ebx, input                   ; start of word  
        mov     eax, (input + length - 1)    ; end of word                                   

        mov     ecx, (length / 2)             ; check will run for half of the word           
check:
        mov     dl, [ebx]                     ; compare first and last letters                
        cmp     [eax], dl
        jne     failure
        inc     ebx
        dec     eax
        loop    check

        ;; success                                                                  
        mov     edx, len1         
        mov     ecx, msg1      
        mov     ebx, 1          
        mov     eax, 4
        int     0x80    
        
        add     esp,4
        jmp     done

failure:
        mov     edx, len2        
        mov     ecx, msg2      
        mov     ebx, 1          
        mov     eax, 4
        int     0x80  
        
        add esp,4

done:
        ret
        
strlen:                     
    push    ebx             
    mov     ebx, eax        
 
nextchar:                   
    cmp     byte [eax], 0
    jz      finished
    inc     eax
    jmp     nextchar
 
finished:
    sub     eax, ebx
    pop     ebx             
    ret 

我才刚刚开始学习汇编语言,所以如果有人能帮助我,那就太好了。为此,我一整天都在失去理智。谢谢!

更新: 感谢所有的回答和评论,感谢@/Peter Cordes 提供的有用建议。因此,我想出了以下(完全不同的方法)代码。我还观看了另一个关于如何检查大小写不同的字符串的视频。

除非我正好输入 10 个字符,否则这(对我)不起作用。但是,当我对输入进行硬编码时,即使少于 10 个字符,它也能正常工作。


msg     db "Please provide a word: ",0
len     equ $ - msg

palindrome  db  'The word is a palindrome'
lenP        equ $ - palindrome

notPalindrome   db  'The word is not a palindrome'
lenNP           equ $ - notPalindrome

segment .bss
input   resb  10        ; reserve 10 bytes for input
length  equ $ - input

section .text
    global _start
    
_start:
    mov     edx, len        ; number of bytes to write
    mov     ecx, msg        ; ECX will point to the address of the string msg
    mov     ebx, 1          ; write to the STDOUT file
    mov     eax, 4          ; invoke SYS_WRITE (kernel opcode 4)
    int     0x80
    
    mov     edx, 10         ; number of bytes to read
    mov     ecx, input      ; reserve space for the user's input
    mov     ebx, 0          ; write to the STDIN file
    mov     eax, 3          ; invoke SYS_READ (kernel opcode 3)
    int     0x80            ; start of word  

    add     eax, ecx
    dec     eax
    
capitalizer:
    cmp byte[ecx], 0
    je  finished
    
    mov bl, byte[ecx]           ;start
    mov dl, byte[eax]           ;end
    
    cmp bl, 90
    jle uppercase         ;start is uppercase
    
    sub bl, 32
    
uppercase:
    cmp dl, 90
    jle check               ;end is uppercase
    
    sub dl, 32
    
check:
    cmp dl, bl
    jne fail
    
    inc ecx
    dec eax
    jmp capitalizer
    
finished:
    mov     edx, lenP         
    mov     ecx, palindrome      
    mov     ebx, 1          
    mov     eax, 4
    int     0x80  
    jmp     exit
    
fail:
    mov     edx, lenNP         
    mov     ecx, notPalindrome      
    mov     ebx, 1          
    mov     eax, 4
    int     0x80  
  
 exit:
    mov     eax, 1
    mov     ebx, 0
    int     0x80```

read系统调用returns读取EAX中的字节数。 (或者如果你传递了一个错误的指针,像 -EFAULT 这样的负 errno 代码,如果 fd 没有打开,则 -EBADF 等等)在一个真正的程序中你会编写错误处理代码(也许重试,直到你真正到达 EOF,以防从长输入中提前读取 returns),但在一个玩具程序中,你可以假设一个读取系统调用成功并获取你想要查看的所有数据。

此数据不一定 以 0 结尾,因为您已将完整的缓冲区大小传递给 read1。它可能在缓冲区的最后一个字符中存储了一个非零输入字节。你不能 strlen 缓冲区找到长度而不可能读到最后。


但幸运的是你不需要,记住 read 将输入长度留在 EAX 中。 2

因此在读取系统调用之后,add eax, ecx 使 EAX 成为指向字符串末尾后一位的指针(类似于 C read() + msg) ,而 ECX 仍然指向读取的 arg。所以你们都准备好让它们相互循环,直到它们交叉,标准回文检查算法。

使用 cmp/jnb 作为回文循环底部的循环条件,而不是 slow loop 操作说明。这比计算指针交叉需要多少次迭代更简单;只是循环直到 p < q 为假,其中 p=start; q=end 最初。由于这是作业,我会让你选择参数 cmp

使用指向过去 输入末尾的指针,如 C++ std::vector::end() 是相当普遍的。您将通过 dec eax / movzx edx, byte [eax] 使用它 - 在 读取它之前递减指针 。)

(或者,如果您确实需要,计算出 sub/shr 详细信息以使用读取的 return 值进行计数循环。)

另一个问题:您的输入可能包含换行符。如果您在换行符之前键入 10 个字节,那么读取缓冲区将只有这些字符,没有换行符。

但是在较短的输入上,缓冲区将包含换行符 (0xa),这与第一个字节比较不相等。您可能希望向后循环结束指针 (EAX) 直到找到非换行符,而不是在添加之前使用特殊大小写 cmp eax, length。这会给你留下一个指向最后一个字节的指针,而不是最后一个字节,所以在这样做之后,主回文循环应该在递减指针之前加载。


脚注 1: 实际上你传递了 11,所以读取本身可以写入你缓冲区的末尾。如果你用 length equ $-input 得到 NASM 来为你计算长度,或者 length equ 10 / input: resb length,你就不会有这个问题,也不会有length 在多个地方硬编码。你会 mov edx, length 在读取系统调用之前。

lengthlength: resb 10保留10个字节的space是没有意义的。如果你想要 4 个字节(一个双字整数),但将它保存在内存中是浪费指令。您还差 运行 出寄存器。

脚注 2:fgets 告诉你他们读取了多少字节的 C 函数真的很愚蠢, 但幸运的是 Unix 系统调用 API 并不糟糕。知道你的数据有多大是正常的,所以尽可能利用指针+长度而不是调用或实现 strlen。

C 库的某些部分可以追溯到 非常 早期的 C 历史,可能在它被称为 C 之前。这部分解释了函数的怪异设计,例如 fopen 采用字符串而不是位常量的 OR (Why does C's "fopen" take a "const char *" as its second argument?), and the bad design of functions like strcpy which finds the length but chooses not to return it. (strcpy() return value)。这就像库设计者讨厌效率,或者极端重视代码大小(总是传递隐式长度的字符串,从不跟踪它们的长度),或者没有意识到当你想要结束时滚动你自己的复制循环不会可行的。 (简单的可移植 C 编译成比手写字符串函数更慢的 asm。)