Home > Net >  How to output string char by char in TASM?
How to output string char by char in TASM?

Time:10-24

I write a program for DOSBox (using TASM). I need to output inputted string as characters line by line. I figured out how to input string, but I have wrong output.

There are 2 problems:

  1. I do not know how to get length of the string, so there are too much blank lines.
  2. There is not one character per line in the output.

My code:

.model small
.data
    message db   'String: $'
        string db 10 dup(' '), '$' 
.stack 256h
.code
main:
    mov ax, @data
    mov ds, ax

        lea dx, message ; load message to dx
    mov ah, 09h ; output message
        int 21h

        xor dx, dx
        lea dx, string ; input string
    mov ah, 0Ah
    int 21h

        ; crlf
        mov dl, 10
        mov ah, 02h
        int 21h
        mov dl, 13
        mov ah, 02h
        int 21h
        
        ; output string char by char 
        mov si, 0
        mov cx, 10 ; a number of loops, but how to get the length of the string?
        output:
              lea dx, string[si]
              mov ah, 09h
              int 21h

              mov dl, 10
              mov ah, 02h
              int 21h
              mov dl, 13
              mov ah, 02h
              int 21h
              
              inc si
        loop output

    mov ah,4ch
    int 21h
end main

CodePudding user response:

The other answer provided a working solution that is based on a string terminator like 0 or 13.
This answer chooses to use the string length as it is already available from DOS, and also more in line with what the OP is asking about.


.data
  message db   'String: $'
  string db 10 dup(' '), '$' 
.stack 256h

"I figured out how to input string, ..." Not really!
That string line is supposed to define the input structure for the DOS.BufferedInput function 0Ah. DOS expects to find the storage length in the first byte and will return the length of the actual input in the second byte. How buffered input works has the details.
What you have written for string translates to a 10-byte region of memory entirely filled with the number 32 (ASCII of ' '), and optimistically followed by a dollar character (the linked post explains why this is not a good idea). Because the first byte is 32, it will permit DOS to legally use the next 34 bytes for inputting purposes. You have a serious buffer overflow!
If you want to allow for an user input of 10 characters, then the correct definition is string db 11, 0, 11 dup(0).


There are 2 problems:

  1. I do not know how to get length of the string, so there are too much blank lines.
  2. There is not one character per line in the output.
  1. DOS already gave you the length of the string in the second byte of the string input structure. Just fetch it: xor cx, cx mov cl, string[1].
  2. Your code (lea dx, string[si] mov ah, 09h int 21h) is using a DOS function (09h) that outputs a string of characters. What did you expect? Use a single character output function instead: mov dl, string[si] mov ah, 02h int 21h.

Your code (with comments)

 lea  dx, message
 mov  ah, 09h
 int  21h
 lea  dx, string
 mov  ah, 0Ah
 int  21h
 mov  dl, 10             ; (*)
 mov  ah, 02h
 int  21h
 ; output string char by char 
 mov  si, 2              ; Characters start at offset 2
 xor  cx, cx
 mov  cl, string[1]      ; Count of characters
 jcxz done
output:
 mov  dl, string[si]     ; Fetch one character
 mov  ah, 02h
 int  21h                ; Print one character
 mov  dl, 13
 mov  ah, 02h
 int  21h                ; Print carriage return
 mov  dl, 10
 mov  ah, 02h
 int  21h                ; Print linefeed
 inc  si                 ; Move to next character
 loop output             ; Repeat for all characters
done:

(*) At the conclusion of the DOS.BufferedInput function 0Ah, the cursor will be in the first column on the current row. You don't need to output a carriage return (13). Just a linefeed (10) will do fine. This is certainly true for a regular MS-DOS, but as @ecm wrote in her comment exceptions to the rule exist.


My code

The above output loop does 3 system calls per iteration. If we reduce this number to 1, the loop can run about 10% faster1.
On each iteration we copy the current character to a $-terminated string that has the carriage return and linefeed bytes included. A single invokation of the DOS.PrintString function 09h then does the outputting. This speed gain does come with one drawback: if the input string has an embedded $ character, it will not get displayed.

1 True as long as the screen does not have to scroll, because screen scrolling is comparatively very slow.

.data
message db 'String: $'
string  db 11, 0, 11 dup(0) 
TheChar db 0, 13, 10, '$'

 ...

 lea  dx, message
 mov  ah, 09h
 int  21h
 lea  dx, string
 mov  ah, 0Ah
 int  21h
 mov  dl, 10             ; (*)
 mov  ah, 02h
 int  21h
 ; output string char by char 
 mov  si, 2              ; Characters start at offset 2
 lea  dx, TheChar
 xor  cx, cx
 mov  cl, string[1]      ; Count of characters
 jcxz done
output:
 mov  al, string[si]     ; Fetch one character
 mov  TheChar, al
 mov  ah, 09h
 int  21h                ; Print one character plus CR plus LF
 inc  si                 ; Move to next character
 loop output             ; Repeat for all characters
done:

CodePudding user response:

Here is how to print a string char by char:

Declare the string such as:

str: db "Hello, world!", 0

Then, to get the length of the string you should search for the null terminator at the end of the string. Either way the code could be something like:

;
; output string char by char
; input: pointer to string in si
;
printstr:
    mov ah, 0x02 ; get 0x02 inside ah to use with int 21h
printstrloop:
    cmp byte[si], 0 ; check if we are at the end of string
    je printstrend ; end if it is
    mov dl, byte[si] ; get the char inside dl to print it
    int 0x21 ; output

    mov dl, 0x0d ; newline character CR
    int 0x21 ; do newline

    mov dl, 0x0a ; newline character Lf
    int 0x21 ; do newline

    inc si ; increase the pointer
    jmp printstrloop ; loop

printstrend:
    ret

You can then do the input like:

Reserve space for string

length: resb 1
actualLength: resb 1
str: resb 255 ; I don't know TASM syntax. This is in NASM

Get the input

mov byte[length], 0xFF
mov ah, 0x0a
mov dx, length
int 0x21

Then you have a CR terminated string at str and the length at actualLength. Then you can use the printstr with cmp byte[si], 0x0d instead of the null terminator

  • Related