【问题标题】:Interchange Letters From String in Assembly Language 8086在汇编语言 8086 中交换字符串中的字母
【发布时间】:2019-12-29 18:32:30
【问题描述】:

我有一个非常简单的问题需要解决。首先输入一个字符串,第一个输出应该复制字符串的最后一个字母并替换字符串的第一个字母,然后最后一个字母应该用第一个字母替换。 第二个输出应该大写字符串的第一个字母。我已经做了第二个输出,我现在的问题是第一个输出。请看下面的预期结果。

预期结果

Enter string: jon jones
son jonej
Jon jones

当前代码

.MODEL SMALL
.STACK 100H
.DATA        
    INPUT_STRING          DB 10,13,"Enter string: $"    
    USER_INPUT_STRING     DB 80 DUP('$') 
    BREAKLINE             DB 10, 13, "$" 
.CODE
    MOV AX, @DATA
    MOV DS, AX  

    LEA DX,INPUT_STRING
    MOV AH,09H
    INT 21H

    LEA DX, USER_INPUT_STRING
    MOV AH, 0AH
    INT 21H  

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H  

    SUB USER_INPUT_STRING + 2, 32       ;Capitalize
    MOV AH, 02H
    INT 21H 

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H  

    LEA DX, USER_INPUT_STRING + 2       ;Output of capitalize
    MOV AH, 09H
    INT 21H

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H       

    MOV AH, 4CH
    INT 21H
END

允许的命令

mov, lea, int, inc, dec, add, sub, proc, re, db

【问题讨论】:

  • @MichaelPetch。 MASM
  • 是的,我正在使用emu8086
  • 对不起。不,我正在使用 emu8086
  • @MichaelPetch。那就是被问到的确切问题。您要输入一个字符串,然后输出字母交换和首字母大写
  • 您的代码甚至不符合声称给您的规则。

标签: assembly ascii dos x86-16


【解决方案1】:

Int 21/AH=0Ah的缓冲区有大小、长度、字符串三部分。大小是字符串的最大大小,必须初始化。

改变

USER_INPUT_STRING     DB 80 DUP('$')

USER_INPUT_STRING     DB 80, 0, 80 DUP('$')

考虑一下,字符串从USER_INPUT_STRING + 2 开始。有它的第一个字符。输入字符串后,您将在USER_INPUT_STRING + 1 中找到您输入的字符串的长度,在本例中为09h。因此,您将在USER_INPUT_STRING + 2 + (9 - 1) 找到输入字符串的最后一个字符。使用寄存器交换这些内​​存地址的值:

.MODEL SMALL
.STACK 100H
.DATA
    INPUT_STRING          DB 13,10,"Enter string: $"
    USER_INPUT_STRING     DB 80, 0, 80 DUP('$')
    BREAKLINE             DB 13, 10, "$"
.CODE
    MOV AX, @DATA
    MOV DS, AX

    LEA DX,INPUT_STRING
    MOV AH,09H
    INT 21H

    LEA DX, USER_INPUT_STRING
    MOV AH, 0AH
    INT 21H

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H

    MOV AL, USER_INPUT_STRING + 2
    XOR CX, CX
    MOV CL, USER_INPUT_STRING + 1
    MOV BX, OFFSET USER_INPUT_STRING + 2
    ADD BX, CX
    DEC BX
    MOV AH, [BX]
    MOV [BX], AL
    MOV USER_INPUT_STRING + 2, AH

    LEA DX, USER_INPUT_STRING + 2
    MOV AH, 09H
    INT 21H

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H

    MOV AX, 4C00H
    INT 21H
END

避免使用方括号的唯一方法,我在LODSBMOVSB 的使用中看到:

.MODEL SMALL
.STACK 100H

.DATA
    INPUT_STRING          DB 13,10,"Enter string: $"
    USER_INPUT_STRING     DB 80, 0, 80 DUP('$')
    BREAKLINE             DB 13, 10, "$"

.CODE
main PROC
    MOV AX, @DATA
    MOV DS, AX
    MOV ES, AX

    LEA DX,INPUT_STRING
    MOV AH,09H
    INT 21H

    LEA DX, USER_INPUT_STRING
    MOV AH, 0AH
    INT 21H

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H

    CALL swap

    LEA DX, USER_INPUT_STRING + 2
    MOV AH, 09H
    INT 21H

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H

    MOV AX, 4C00H
    INT 21H
main ENDP

swap PROC
    LEA DI, USER_INPUT_STRING + 2
    MOV AL, USER_INPUT_STRING + 1
    MOV AH, 0
    SUB AL, 1
    ADD DI, AX
    MOV SI, DI
    LODSB
    MOV AH, USER_INPUT_STRING + 2
    XCHG AL, AH
    STOSB
    MOV USER_INPUT_STRING + 2, AH
    RET
swap ENDP

END main

在 EMU8086 和 TASM 中(不是在 MASM 中)您还可以使用特殊的预处理算法:USER_INPUT_STRING + 2 + BX - 1:

.MODEL SMALL
.STACK 100H

.DATA
    INPUT_STRING          DB 13,10,"Enter string: $"
    USER_INPUT_STRING     DB 80, 0, 80 DUP('$')
    BREAKLINE             DB 13, 10, "$"

.CODE
main PROC
    MOV AX, @DATA
    MOV DS, AX
    MOV ES, AX

    LEA DX,INPUT_STRING
    MOV AH, 09H
    INT 21H

    LEA DX, USER_INPUT_STRING
    MOV AH, 0AH
    INT 21H

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H

    CALL swap

    LEA DX, USER_INPUT_STRING + 2
    MOV AH, 09H
    INT 21H

    LEA DX, BREAKLINE
    MOV AH, 09H
    INT 21H

    MOV AX, 4C00H
    INT 21H
main ENDP

swap PROC
    MOV AH, USER_INPUT_STRING + 2
    MOV BL, USER_INPUT_STRING + 1
    MOV BH, 0
    MOV AL, USER_INPUT_STRING + 2 + BX - 1
    MOV USER_INPUT_STRING + 2, AL
    MOV USER_INPUT_STRING + 2 + BX - 1, AH
    RET
swap ENDP

END main

所有程序都会更改字符串的内容。要撤消此操作,您必须再次call swap。第二部分由您决定。

【讨论】:

  • 您可以通过使用像[BX-1] 这样的寻址模式而不是首先使用dec bx 来简化此操作。或者,如果您正在优化代码大小,则将 BX 设置为首先指向第一个字符,以便您可以使用 [BX-1] 访问长度。在某些步骤中评论 BX 的价值可能会很好。
  • @rkhb。我不允许在 [BX] 中使用 XOR 和 []。你能替换你的一些代码吗?谢谢
  • @Joseph:如果您有荒谬的任意限制,请将它们放在问题中。 xor cx,cx 只是将 CX 归零,因此加载 CL 会将值归零到 CX。您可以以任何方式将 CX 归零,例如sub cx,cxmov cx,0。至于 BX 与其他寄存器,您需要一种寻址模式,其中有一个寄存器来寻址字符串的末尾。因此,如果您不想使用 BX,请选择不同的。
  • @PeterCordes。很抱歉,只有 [BX] 中的 []。
  • @rkhb。还有一件事,你忘记了大写,你删除了它。也许您可以为此使用 PROC。非常感谢rkhb
【解决方案2】:

只是为了好玩,这是一个受 rkhb 回答启发的优化版本。在这个和 rkhb 之间的某个地方会是一个更简单的版本,说明更少,但不会让人难以理解。


DOS int 21h / AH=0Ah 接受一个指向结构的指针,而不仅仅是一个平面缓冲区。前 2 个字节是缓冲区大小和长度。 (由于某种原因,DOS 函数存储长度而不是在 AL 中返回它)。文档:http://spike.scu.edu.au/~barry/interrupts.html#dosbuf

您应该使缓冲区大于 大于您指定的最大长度,因此在输入最大长度后它仍然是$-终止的。显然,在用户输入后,缓冲区中会留下 CR,但不会保留 CR LF,如果可以保证用户输入达到最大长度而不是用户按返回结束,则 IDK 会保留。 DOS 留在缓冲区中的输入大小不包括 CR。您的版本使用 $ 的预填充缓冲区最终将打印 CR CR LF,因为它使用终止符,而不是长度,但这可能不是输出到屏幕的问题。

你可以让它再大 2 个字节,这样你就有空间自己附加一个 CRLF,而不是需要通过单独的调用来打印它。由于您得到的长度不包括 CR,因此很容易覆盖它并在用户输入后仅保留 CR LF,在第一个 $ 之前。


优化:

首先,您可以将其设为.com 可执行文件,这样您的所有段寄存器都已正确设置;使用.model tiny。 (除此之外,您几乎可以保证将数据放在代码旁边,从而导致自修改代码管道停止。)

您也不需要 LEA 将静态地址放入寄存器。 mov dx, OFFSET INPUT_STRING 短 1 个字节。没有没有寄存器的[disp8]寻址模式。


使用add [mem], 20hor [mem], 20h 将第一个字符恢复为小写后,您又回到了原来的状态。 (假设输入字符实际上是小写,而不是原来的大写)。


您想加载长度,以便知道 last 字符在哪里。你被 8086 困住了,所以你不能只使用 movzx cx, byte ptr [bx] 将其零扩展为 16 位寄存器;您必须将一个 16 位寄存器归零,然后将一个字节合并到低半部分。 (或其他可能性)。

您还希望在某个时候将指针放在寄存器中,因此您不妨尽早(在 SI 或 DI 或 BX 中)这样做,这样您就可以使用更紧凑的寻址模式,甚至使用 mov dx, bx而不是 mov dx,OFFSET USER_INPUT_STRING`。虽然地址只有 16 位,但不值得为此花费额外的指令。

这是有趣的部分;从读取用户输入开始。

.DATA
max_user_len = 80           ; assemble time constant, not stored in memory by this line

    ; +3 extra $ chars means we can append a CRLF and *still* have it $-terminated
    ; after a max-length user input
    input_buf             DB max_user_len, 0,  max_user_len+3 DUP('$')
 ; notice that the end of the buffer is far below 256 bytes into the .data segment
 ; which makes address math with 8-bit registers safe.
 ; this is a hack which can break if you link more things together and have a bigger data segment.

    prompt_string         DB 13,10,"Enter string: $"
    BREAKLINE             DB 13, 10, "$"

.CODE
 main:

    ... prompt and stuff same as before
    ; then the interesting part

    mov DX,  OFFSET input_buf
    MOV AH, 0AH                  ; DOS buffered input
    INT 21H

;;; you may need to print a CR LF here, according to Michael Petch's comment
;;; pressing enter doesn't echo the newline

  ;; load from the input
    mov    si,  OFFSET input_buf + 2   ; pointer to first data char
    mov    cx, [si-1]                    ; CL = length,  CH=original first char

  ;; append a CR LF to the end of the buffer.
    mov    bx, si
    add    bl, cl                ; HACK: the whole buffer is in the first 256 bytes of the segment and thus we don't need carry propagation into the high half
     ; BX points to one past the end user input.
    mov  word ptr [bx], 0A0Dh    ; append CR LF = 0D 0A = little-endian 0x0A0D.  Still $-terminated because we have extra padding.

  ;; first output, including a CR LF
                                 ; SI still points at the first char
    and   byte ptr [si], ~20h    ; clear the ASCII-lowercase bit
    mov    dx, si
    mov    ah, 09h
    int    21h                   ; DOS buffered output: printing just the text.

  ;; swap first and last
  ;; We still have the original first char already loaded (CH)
  ;; just need to load the last char and then store both to opposite places.

    mov    al, [bx-1]            ; last char before CR LF $
    mov    [bx-1], ch            ; replace it with orig first char
    mov    [si], al              ; store last char

    ; DX = output buffer, AH = 0Ah  from last time
    ; second output
    int   21h 

    ; exit
    mov   ax, 4c00H
    int   21h

    ret

未经测试,某处可能有一个 off-by-1 错误。

这主要针对代码大小最重要的 8086 进行了“优化”。否则,我可能会复制和修改 CH 并将其存储,而不是使用内存目标 and,这将不得不再次从缓存/内存中重新加载该字节。

xchg [bx-1], ch 会更小,但隐含的lock 前缀会使其变慢。

【讨论】:

  • 谢谢彼得。但它更复杂:)
  • 如果你想让它成为一个完整的 DOS COM 示例(虽然 OP 明确使用 EXE)- 在顶部添加 .model tiny。在.CODE 之后,您还需要一个org 100hbuflen+3 DUP('$') 应该是 max_user_len+3 DUP('$')and byte ptr [si], ~20h 不是 MASM 语法,它应该是 and byte ptr [si], NOT 20h 。你的程序没有尽头,所以我至少会考虑在最后一个ret
  • 您的程序没有考虑到的一件事是,在 DOS 中,当您读取 Int 21h/AH=0Ah 的字符串后,CR 会留在缓冲区中,如您所说,但只有一个 CR 被发送到控制台将光标留在当前行的开头。您至少需要在打印第一个字符串之前输出一个 LF,以避免您编写的下一个字符串出现在控制台上输入字符串的顶部。
  • 由于您将其设为 DOS COM 程序,因此您还需要底部的 END main。首选的替代方法是简单地在底部使用END,并将.STARTUP 指令放在.CODE 下。您还必须删除 org 100h 指令。 .STARTUP 将处理将 ORG 设置为 100h 并在使用 tiny 模型时设置入口点。
  • 似乎 OP 已经澄清他们正在使用 EMU8086。它有怪癖。它使用 ~ 与使用 NOT 的 MASM 不同。输入字符串时,EMU8086 也根本不会移动光标,然后按 ENTER 键。因此,您需要发送 CR LF 以确保第一个字符串的输出位于新行的开头。
猜你喜欢
  • 2015-02-26
  • 2022-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多