簡體   English   中英

清除輸入緩沖區組件 x86 (NASM)

[英]Clear input buffer Assembly x86 (NASM)

編輯:這類似於: Reset a string variable to print multitple user input in a loop (NASM Assembly) 但這不是同一個問題。

從另一篇文章中,我能夠防止打印其他字符。 但是,當程序返回到它要求用戶輸入的點時,我仍然無法阻止讀取這些附加字符。


我正在創建一個程序,要求用戶輸入,然后打印它。 之后,如果他們想打印另一個文本,它會要求用戶輸入“y”,或者按任何其他鍵關閉程序。

我的問題是,如果用戶輸入的字符多於預期,這些額外的字符不會消失,當程序返回要求用戶輸入時,沒有機會輸入,因為程序從上次它收到輸入。

例如:

要求用戶輸入要打印的文本,他們輸入:“ Heyyyyyyyyyyyyyyyyyyyyyyyyyyyy

剩余的是“yyy”

此時,程序應要求用戶輸入“y”以重復該過程,或要求用戶輸入任何其他內容以關閉程序。

輸出:

  • 嘿yyyyyyyyyyyyyyyyyyyyyyyyy

  • 想再試一次嗎? 如果是,請輸入 y。 如果沒有,請輸入任何其他內容以關閉程序

  • 輸入您的文字:

輸出:yy

  • 想再試一次嗎? 如果是,請輸入 y。 如果沒有,請輸入任何其他內容以關閉程序。

直到現在它再次要求用戶輸入

由於“yyy”仍在緩沖區中,因此在這種情況下用戶沒有機會實際輸入。

我能做些什么來解決這個問題? 我最近才開始了解這個,所以我很感激任何幫助。

謝謝。

這就是我所做的

prompt db "Type your text here.", 0h
retry db "Wanna try again? If yes, enter y. If not, enter anything else to close the program"

section .bss
text resb 50
choice resb 2

section .text
    global _start

_start:

    mov rax, 1      ;Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, 21
    syscall


    mov rax, 0      ;Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, 50
    syscall

    mov r8, rax     ;This is what I added to prevent additional characters
                    ;from being printed

    mov rax, 1      ;Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1      ;Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, 83
    syscall

    mov rax, 0      ;Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 2
    syscall

    mov r8b, [choice] ;If choice is different from y, go to end and close the program. Otherwhise, go back to start.
    cmp byte r8b, 'y'
    jne end
    jmp _start

    end:
    mov rax, 60      
    mov rdi, 0       
    syscall

清除stdin的簡單方法是檢查choice的第二個字符是否是'\\n' ( 0xa )。 如果不是,則字符保留在stdin未讀。 您已經知道如何從stdin讀取,因此在這種情況下,只需讀取stdin直到讀取'\\n' 1 ,例如

    mov rax, 0      ;Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 2
    syscall

    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end
    
    cmp byte [choice + 1], 0xa  ; is 2nd char '\n' (if yes done, jump start)
    je _start
    
    empty:          ; chars remain in stdin unread
    mov rax, 0      ; read 1-char from stdin into choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 1
    syscall
    
    cmp byte [choice], 0xa  ; check if char '\n'?
    jne empty               ; if not, repeat
    
    jmp _start

除此之外,您應該在聲明它們時確定您的提示長度,例如

prompt db "Type your text here. ", 0h
plen equ $-prompt
retry db "Try again (y/n)? ", 0h
rlen equ $-retry

這樣你就不必硬編碼長度以防你改變你的提示,例如

_start:

    mov rax, 1      ;Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, plen
    syscall


    mov rax, 0      ;Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, 50
    syscall

    mov r8, rax

    mov rax, 1      ;Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1      ;Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, rlen
    syscall

如果你把它放在一起,你可以這樣做:

prompt db "Type your text here. ", 0h
plen equ $-prompt
retry db "Try again (y/n)? ", 0h
rlen equ $-retry

section .bss
text resb 50
choice resb 2

section .text
    global _start

_start:

    mov rax, 1      ;Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, plen
    syscall


    mov rax, 0      ;Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, 50
    syscall

    mov r8, rax

    mov rax, 1      ;Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1      ;Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, rlen
    syscall

    mov rax, 0      ;Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 2
    syscall

    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end
    
    cmp byte [choice + 1], 0xa  ; is 2nd char '\n' (if yes done, jump start)
    je _start
    
    empty:          ; chars remain in stdin unread
    mov rax, 0      ; read 1-char from stdin into choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 1
    syscall
    
    cmp byte [choice], 0xa  ; check if char '\n'?
    jne empty               ; if not, repeat
    
    jmp _start

    end:
    mov rax, 60      
    mov rdi, 0       
    syscall

示例使用/輸出

$ ./bin/emptystdin
Type your text here. abc
abc
Try again (y/n)? y
Type your text here. def
def
Try again (y/n)? yes please!
Type your text here. geh
geh
Try again (y/n)? yyyyyyyyyyyyyeeeeeeeeeesssssssss!!!!
Type your text here. ijk
ijk
Try again (y/n)? n

現在甚至有一只貓在你的(y/n)?上踩鍵盤(y/n)? 提示不會引起問題。 可能有更優雅的方法來處理這個問題,使用syscall重復讀取更有效,但這將解決這個問題。


其他注意事項和錯誤檢查

如上所述,一次簡單地讀取和檢查一個字符並不是一種非常有效的方法,盡管它在概念上是最簡單的擴展,無需進行其他更改。 @PeterCordes在下面的評論中提出了許多與更有效的方法相關的好點,更重要的是關於可能出現的錯誤情況,也應該受到保護。

對於初學者,當您正在尋找有關單個系統調用使用的信息時,系統調用剖析,第 1 部分提供了一些有關接近它們的使用的背景知識,並由 Linux 手冊頁補充,請閱讀man 2 閱讀有關參數類型的詳細信息並返回類型和值。

上面的原始解決方案沒有解決如果用戶通過按Ctrl+d手動生成文件結尾或實際發生讀取錯誤時會發生的情況。 它只是解決了用戶輸入和清空stdin問題。 對於任何用戶輸入,在使用該值之前,您必須通過檢查 return來驗證輸入是否成功。 (不僅適用於是/否輸入,還適用於所有輸入)。 出於此處的目的,您可以將零輸入(手動文件結束)或負返回(讀取錯誤)視為失敗的輸入。

要檢查您是否至少有一個有效的輸入字符,您可以簡單地檢查返回值( read返回讀取的字符數, sys_read將該值放置在系統調用之后的rax )。 零或負值表示未收到輸入。 支票可能是:

    cmp rax, 0              ; check for 0 bytes read or error
    jle error

你可以給用戶寫一個簡短的診斷,然后根據需要處理錯誤,這個例子在輸出診斷后簡單地退出,例如

readerr db 0xa, "eof or read error", 0xa, 0x0
rderrsz equ $-readerr
...
    ; your call to read here
    cmp rax, 0              ; check for 0 bytes read or error
    jle error
    ...
    error:
    mov rax, 1              ; output the readerr string and jmp to end
    mov rdi, 1
    mov rsi, readerr
    mov rdx, rderrsz
    syscall
    
    jmp end

現在轉向一種更有效的方式來清空stdin 原始答案中最大的障礙是重復系統調用sys_read以重用您的 2 字節choice緩沖區一次讀取一個字符。 顯而易見的解決方案是使choice更大,或者只是使用堆棧空間每次讀取更多字符。 (您可以查看幾種方法的注釋)例如,在這里,我們將choice增加到 128 字節,在預期"y\\n"輸入的情況下,將僅使用其中兩個字節,但在輸入過長的情況將一次讀取 128 個字節,直到找到'\\n' 對於設置,您有:

choicesz equ 128
...
section .bss
text resb 50
choice resb 128

現在在你要求(y/n)? 你的閱讀將是:

    mov rax, 0              ; Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall

    cmp rax, 0              ; check for 0 bytes read (eof) or error
    jle error
    
    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end                 ; not a 'y', doesn't matter what's in stdin, end

現在有兩個條件需要檢查。 首先,將讀取的字符數與緩沖區大小choicesz ,如果讀取的字符數小於choicesz ,則stdin沒有未讀取的字符。 其次,如果返回值等於緩沖區大小,則stdin可能有也可能沒有剩余字符。 您需要檢查緩沖區中的最后一個字符以查看它是否是'\\n'以指示您是否已閱讀所有輸入。 如果最后一個字符不是'\\n'字符仍然未讀(除非用戶碰巧在第 128 個字符處生成手動文件結尾)您可以檢查為:

    empty:
    cmp eax, choicesz       ; compare chars read and buffer size
    jb _start               ; buffer not full - nothing remains in stdin
    
    cmp byte [choice + choicesz - 1], 0xa   ; if full - check if last byte \n, done
    je _start
    
    mov rax, 0              ; fill choice again from stdin and repeat checks
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall
    
    cmp rax, 0              ; check for 0 bytes read or error
    jle error

    jmp empty

注:如上面提到的,還有一個進一步的情況下,以覆蓋,這里沒有涉及,例如其中用戶輸入有效的輸入,但隨后生成一個手動結束文件,而不是只按第128字符之后輸入(或128 的倍數)。在那里你不能只尋找一個'\\n'它不存在,如果沒有更多的字符並再次調用sys_read ,它會阻止輸入。可以想象你將需要使用一個非阻塞讀取和放回單個字符以打破這種歧義——這是留給你的)

具有改進的完整示例是:

prompt db "Type your text here. ", 0x0
plen equ $-prompt
retry db "Try again (y/n)? ", 0x0
rlen equ $-retry
textsz equ 50
choicesz equ 128
readerr db 0xa, "eof or read error", 0xa, 0x0
rderrsz equ $-readerr

section .bss
text resb 50
choice resb 128

section .text
    global _start

_start:

    mov rax, 1              ; Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, plen
    syscall

    mov rax, 0              ; Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, textsz
    syscall
    
    cmp rax, 0              ; check for 0 bytes read or error
    jle error

    mov r8, rax

    mov rax, 1              ; Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1              ; Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, rlen
    syscall

    mov rax, 0              ; Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall

    cmp rax, 0              ; check for 0 bytes read (eof) or error
    jle error

    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end                 ; not a 'y', doesn't matter what's in stdin, end
    
    empty:
    cmp eax, choicesz       ; compare chars read and buffer size
    jb _start               ; buffer not full - nothing remains in stdin
    
    cmp byte [choice + choicesz - 1], 0xa   ; if full - check if last byte \n, done
    je _start
    
    mov rax, 0              ; fill choice again from stdin and repeat checks
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall
    
    cmp rax, 0              ; check for 0 bytes read or error
    jle error

    jmp empty
    
    end:
    mov rax, 60      
    mov rdi, 0       
    syscall
    
    error:
    mov rax, 1              ; output the readerr string and jmp to end
    mov rdi, 1
    mov rsi, readerr
    mov rdx, rderrsz
    syscall
    
    jmp end

肯定有更有效的方法來優化它,但是為了討論“我如何清空stdin ?”,使用緩沖區大小的第二種方法可以避免重復調用sys_read以一次讀取一個字符是向前邁出了一大步。 “它如何完全優化檢查?” 是一個單獨的問題。

如果您還有其他問題,請告訴我。

腳注:

1.在用戶輸入 input 的這種情況下,用戶通過按Enter生成'\\n' ,允許您檢查'\\n'作為清空stdin的最后一個字符。 用戶還可以通過按Ctrl+d 來手動生成文件結尾,因此不能保證'\\n' 還有許多其他方法可以填充stdin ,例如將文件重定向為輸入,其中應該有一個結尾'\\n'以符合 POSIX 標准,這也不能保證。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM