检查用户的输入是否为回文
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 结尾,因为您已将完整的缓冲区大小传递给 read
1。它可能在缓冲区的最后一个字符中存储了一个非零输入字节。你不能 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
在读取系统调用之前。
为length
和length: 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。)
我遇到一段代码,其中汇编程序可以检查字符串是否为回文,但字符串是硬编码的。我想练习和修改代码来做同样的事情,除了程序将接受用户输入的字符串。
我从这个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 结尾,因为您已将完整的缓冲区大小传递给 read
1。它可能在缓冲区的最后一个字符中存储了一个非零输入字节。你不能 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
在读取系统调用之前。
为length
和length: 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。)