在汇编语言 8086 中交换字符串中的字母

Interchange Letters From String in Assembly Language 8086

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

预期结果

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

Int 21/AH=0Ah的缓冲区由三部分组成:size、length、string。大小是字符串的最大大小,必须初始化。

改变

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。是否合并第二部分由您决定。

纯属娱乐,这里是受 rkhb 回答启发的优化版本。介于这个和 rkhb 之间的某个地方是一个更简单的版本,指令更少,但不会让人难以理解。


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

您应该使您的缓冲区 大于您指定的最大长度,这样它在最大长度输入后仍然 $ 终止。显然,在用户输入后,缓冲区中会保留一个 CR,但不会保留 CR LF 和 IDK,如果可以保证用户输入达到最大长度而不是以用户点击 return 结束。 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,这主要是 "optimized"。否则我可能会复制和修改 CH 并存储它,而不是使用内存目标 and,这将不得不再次从 cache/memory 重新加载该字节。

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