Printing out a simple string in a routine replacing a character in it

Started by Huskypaw, May 29, 2009, 09:25:37 PM

Previous topic - Next topic

Huskypaw

I got a string "Wind" and am iterating through it until the character being pointed at matches "i" and gets exchanged with "a". Now what I want is to every time print out "Wind" and afterwards, if this works, print out the part beginning from the actual pointer to the end of my string. Here is my example code:
DATA SEGMENT
String db "Wind"
db "$"
length equ $ - String
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE,DS:DATA
mov ax,DATA
mov ds,ax

mov bx,offset String
mov al,length

routine:
lea dx,String
mov ah,9
int 21h

cmp word ptr [bx],"i"
je found
inc bx
dec al
cmp al,0
jne routine
je end
found:
mov word ptr [bx],"a"
jmp end

ende: mov ah,4Ch
      int 21h
CODE ENDS
END routine


The problem is, code seems to be working fine at exchanging "i" with "a", but it doesn't print out "Wind" every time the character isn't being found or "Wand", instead it prints out something like "§$%&$§/&§$%&DFSGerwer"§$Wind§$%§46534545".

What am I doing wrong?

dedndave

lea dx,String

use this instead...

mov dx,offset String


cmp word ptr [bx],"i"

is this what you really want ? that will only be true if the "i" is followed by a 0 byte
try this.....

cmp byte ptr [bx],"i"

same for this line......

mov word ptr [bx],"a"

"a" and "i" are bytes, normally

MichaelW

Also:

Length and end are reserved words.

Routine will not work as an entry point label because the first four instructions are necessary and they will be skipped.

The interrupt call will overwrite AL so you cannot use it as a counter. I suggest using CX instead.

After you DEC the counter there is no need to compare the counter to zero, because DEC will set the zero flag when the counter reaches zero. Something like this would be preferable:

dec cx
jnz routine

eschew obfuscation

dedndave

the loop instruction will do that for you if the count is in CX

loop routine

your "length" variable includes the $ terminator
that is why you see garbage out - it is being overwritten and when you display the
string, it will display text until it happens to run across a $ in the code or memory remnants

   mov cx,lngth-1

routin:
;
;
   push cx
   int 21h
   pop cx
;
;
   loop routin

this will pass through the loop "lngth-1" times

although the CX register may be preserved across the INT 21h call, there is no guarantee
it is not documented anywhere that it will be saved for you (even though it is, in this case)
if you want a register preserved, save it yourself to be on the safe side
it will save you many hours of pulling your hair out
there are a few exceptions, of course


Huskypaw

Okay, so I changed the code to
DATA SEGMENT
String db "Wind"
db "$"
lngth equ $ - String
DATEN ENDS

CODE SEGMENT
ASSUME CS:CODE,DS:DATA
mov ax,DATA
mov ds,ax

mov bx,offset String
mov cx,lngth - 1

routine:
mov dx,offset String
mov ah,9

push cx
int 21h
pop cx

cmp byte ptr [bx],"i"
je found
inc bx
dec cx
jnz routine
je stop

found:
mov byte ptr [bx],"a"
jmp stop

stop: mov ah,4Ch
      int 21h
CODE ENDS
END routine


but it still puts garbage on the screen, with "Wind" in between!

I found out that "Wind$" is found in DS:0100h, but somehow the assembler puts 0 into bx instead of 0100h. Strangewise, CX = 0 at entry point and gets decremented to FFFFh.

Does someone know the answer?

QuoteAfter you DEC the counter there is no need to compare the counter to zero, because DEC will set the zero flag when the counter reaches zero. Something like this would be preferable:

dec cx
jnz routine
I did as you said and the assembler changes
JNZ
to
CMP Byte Ptr [BX],69
JZ 001F
,
which are ten bytes and as many as bytes as in
CMP Byte Ptr [BX],69
JZ 0022
,
so there only should be a reading advantage in changing this?

MichaelW

The main reason you are getting garbage is that you are still using the routine label as an entry point, so DS, BX and CX are not being initialized. To ensure that the instructions above the routine label execute, you need to place another label, I suggest start, above the mov ax,DATA statement, and modify your END directive accordingly.

BTW, you could have easily detected this problem, and probably others, by simply assembling, linking, and then testing the EXE with DEBUG.
eschew obfuscation

Huskypaw

Thanks for your help in this. Assembling, linking and debugging is exactly where I got my information in my previous post from!
DATA SEGMENT
String db "Wind",0Ah
db "$"
lngth equ $ - String
DATA ENDS

CODE SEGMENT
start:
ASSUME CS:CODE,DS:DATA
mov ax,DATA
mov ds,ax

lea bx,String
mov cx,lngth

routine:
lea dx,[bx]
mov ah,9

push cx
int 21h
pop cx

cmp byte ptr [bx],"i"
je found
inc bx
loop routine

found:
mov byte ptr [bx],"a"
lea dx,String
int 21h
mov ah,4Ch
int 21h
CODE ENDS
END start

is the actual result of this thread.

The output is the following:
Wind
    ind
       Wand

dedndave

you can replace the LEA instructions as follows....

;------------------------------------------

DATA    SEGMENT

String db "Wind"
lngth equ $ - String

       db 0Dh,0Ah,24h

DATA    ENDS
;------------------------------------------

CODE    SEGMENT
        ASSUME CS:CODE,DS:DATA

start:
        mov     ax,DATA
        mov     ds,ax

        mov     bx,offset String
        mov     cx,lngth

routine:
        mov     dx,bx
        mov     ah,9
        push    bx
        push    cx
        int     21h
        pop     cx
        pop     bx

        cmp byte ptr [bx],"i"
        je      found

        inc     bx
        loop    routine

found:
        mov byte ptr [bx],"a"
        mov     dx,offset String
        mov     ah,9
        int     21h
        mov     ax,4C00h
        int     21h

CODE ENDS

;------------------------------------------

        END     start

Huskypaw

I see why I should replace it in 'routine', but I don't see why I should do it in 'start' and in 'found'?

dedndave

lea is "Load Effective Address"

it caclulates the effective address refered to in the source operand and places it in the destination operand
it is used for things like this......

lea si,[bx+100h]

esi will be = ebx+100h

but, in your cases, you have no calculation to make - they are simple offsets
that means you can just use

mov si,bx (in my example)
or
mov bx,offset String

mov is a smaller and faster instruction

notice that "offset String" is really just a 16-bit number
if you were to disassemble it, it would look like this

mov bx,1780h   (assuming the address of String is 1780h)

if i wanted to get "offset String+100h" into a register, i would again not need to use LEA
the assembler performs the addition (address of String) + (100h) and creates the code for
mov bx,offset String+100h
mov bx,1880h

however, in the case of "[bx+100h]", the assembler has no idea what value is in bx, so i would have to use LEA

EDIT - did you notice the other changes i made ?

Huskypaw

You saved bx on the stack and put 'db 0Dh,0Ah,24h' after you defined the length. How is this possible since the string isn't at it's end after length, but before and why did you save the register?

dedndave

well - just a good habit to save registers across int 21 calls - not needed always

the 0Dh,0Ah,24h is a carriage return, a line feed, and a "$" string terminator

you had only a line feed, which goes to the next line but does not change the column
carriage return sets the column to 0 for the next character
you will see 0Dh and 0Ah (or 13,10 decimal) together quite often

i moved the "lngth" label so that it would not calculate the string length with the line feed
you just want to run through the loop 4 times, not 5

i also changed mov ah,4Ch to mov ax,4C00h
this is not that critical, but DOS accepts a return code in AL
if the program were run in a batch file, the return code value can be recovered
if you do not set the value in AL, the return code will be whatever value is in AL at the time
it is just good practice to set it to 0, indicating "no error", if you do not use it otherwise

Huskypaw

After passing exams and further reading sessions I had a few minutes left to change my code into this:
DATA SEGMENT
S_Question db "Input: ", "$"
introduction db "String before:", 0Ah, "$"
outtro db 0Ah,"String after:", 0Ah, "$"
String db "Wind",0Ah, "$"
lngth equ $ - String
LF db 0Ah,"$"
answer db "1234567890", "$"
DATA ENDS

CODE SEGMENT
start:
ASSUME CS:CODE,DS:DATA
mov ax,DATA
mov ds,ax

mov dx,offset S_Question
call printString

mov dx, offset answer
call readString

mov dx,offset LF
call printString ; line feed for readabillity
call printString ; line feed for readabillity

;mov dx, offset introduction
mov dx, offset answer
call printString

mov bx,offset String
mov cx,lngth

mov dx,bx
call printString

routine:
cmp byte ptr [bx],"i"
je found
inc bx
loop routine

found:
mov dx, offset outtro
call printString
mov byte ptr [bx],"a"
mov dx,offset String
call printString
mov ah,4Ch
int 21h
;*******************************************************
;subfunction for the input of a string
;from keyboard.
;input parameters:
;output parameters:
;changed registers:
;*******************************************************
readString PROC NEAR
mov ah,0Ah
int 21h
ret
readString ENDP
;******************************************************
;subfunction for printing a char array
;which is terminated by "$"-char
;output parameters: none
;changed registers: AH,DX
;******************************************************
printString PROC NEAR
push ax
mov ah,09h
int 21h
pop ax
ret
printString ENDP

CODE ENDS
END start


Actually, I'm trying to expand my code so that the inputted string will be the word in which the char is be to replaced. But I don't know how to overwrite the field answer or string with the input?
I thought the subfunction readString could be of use to me, but it just won't work. Any suggestions?

dedndave

without looking to hard at your code...
you need to set up an input data buffer
it has been a while since i used DOS line input, as i always wrote my own routines using BIOS INT 16h calls
but, i think a 128 character buffer is adequate

dedndave

here are two methods of DOS line input...

first method:

INT 21h Function 0Ah:
Read buffered array from standard input
Requires a predefined structure to be set up that describes the maximum input size and holds the input characters.
Example:

count = 80

KEYBOARD STRUCT

      maxInput BYTE count  ; max chars to input

      inputCount BYTE ?  ; actual input count

      buffer BYTE count DUP(?)  ; holds input chars

KEYBOARD ENDS

.data

kybdData KEYBOARD <>

.code

      mov ah,0Ah

      mov dx,OFFSET kybdData

      int 21h

second method:

INT 21h Function 0Bh:
Get status of standard input buffer

L1: mov ah,0Bh ; get buffer status

      int 21h

cmp al,0 ; buffer empty?

je L1 ; yes: loop again

      mov ah,1 ; no: input the key

      int 21h

      mov char,al ; and save it

Can be interrupted by Ctrl-Break (^C)