I/O da console


In questo capitolo vedremo come si possono leggere dati dalla tastiera, 
elaborarli e poi visualizzare i risultati.
Anche stavolta vi presentero un semplice programma che andremo poi ad 
analizzare in profondità; il programma legge una stringa e la stampa 
ribaltata.

TUT5.ASM - by b0nuS 1997

SEG_A          SEGMENT
               ASSUME CS:SEG_A, DS:SEG_A
               ORG	100H

Ribalta        PROC	FAR

inizio:        jmp	START	;salta a START

max_len        EQU	1000	;massima lunghezza 
sorgente       db	max_len dup(?)	;stringa da ribaltare
destinaz       db	max_len dup(?)	;stringa ribaltata

START:
               mov	si,OFFSET sorgente
prossimo_car:  mov	ah,01h  	;legge un car dalla tastiera
               int	21h
               cmp 	al,0Dh          	;è = a return ?
               je	fine_lettura	;se si smetti di leggere
               mov    	sorgente[si],al 	;sposta il carattere in sorgente
               inc    	si             	;incrementa si
               jmp	prossimo_car	;leggi il prossimo car
			
fine_lettura:
               mov	cx,si       
               push 	cx	;memorizza la lunghezza nello stack
               mov	bx,OFFSET sorgente
               mov	si,OFFSET destinaz
               add	si,cx	;metto in si la lunghezza della stringa
               dec	si	;decrementa si
		
Ribaltamento:
               mov	al,[bx]	;routine di ribaltamento
               mov	[si],al	; parto dal fondo e la copio
               inc    	bx                      ; in destinaz
               dec	si
               loop	Ribaltamento	; salto a Ribaltamento
		
               pop	si	;prelevo la lunghezza
               mov	destinaz[si+1],'$'	;aggiungo il terminatore
               mov	ah,09h		
               mov	dx,OFFSET destinaz
               int	21h	;stampo la stringa ribaltata
	
               RETN
Ribalta        ENDP

SEG_A          ENDS
               END	inizio

Analizziamolo...
All'inizio  ci sono le solite direttive per il CS e il DS e per l'indirizzo 
iniziale (100h) che ormai conoscete; subito dopo ci sono le dichiarazioni delle
variabili usate :

max_len EQU 1000
----------------------
Questa dichiara una costante max_len che vale 1000 tramite la direttiva EQU.

Sorgente db max_len dup(?)
--------------------------
Questa dichiara una specie di array di byte di lunghezza max_len non 
inizializzato. Conoscete già le direttive DB e DUP , questo è un ulteriore modo 
di combinarle.La stessa istruzione in C sarebbe:

	char Sorgente[max_len];

L'istruzione successiva è la stessa cosa.
Dopo queste dichiarazioni si comincia con il vero programma:

                        mov   si,OFFSET sorgente
prossimo_car:           mov   ah,01h           ;legge un car dalla tastiera
                        int   21h
                        cmp   al,0Dh           ;è = a return ?
                        je    fine_lettura	;se si smetti di leggere
                        mov   sorgente[si],al  ;sposta il carattere in sorgente
                        inc   si               ;incrementa si
                        jmp   prossimo_car	;leggi il prossimo car

questo frammento di programma legge dalla tastiera una stringa carattere per 
carattere; si utilizza l'int 21h,01h che ritorna il carattere letto in al.
Dopo la chiamata all'int 21h si controlla il carattere letto e se è RETURN 
(codice ascii 13 = 0Dh) si termina il ciclo di lettura.
Il controllo del carattere si effettua tramite l'istruzione cmp (compare) che 
confronta i due operandi , se questi sono uguali cmp setta a 1 lo Zero Flag.
L'istruzione successiva è un salto condizionato: "salta se sono uguali".
Questa istruzione controlla il valore dello Zero Flag se è uno salta 
all'etichetta indicata altrimenti prosegue con l'istruzione successiva.
Esistono vari tipi di salti condizionati , ve ne presento qui di seguito i piu' 
utilizzati :
 
JE  - Salta se sono uguali (ZF = 1)
JNE - Salta se sono diversi (ZF = 0)

JZ -  Salta se è zero (ZF = 1)
JNZ - Salta se non è zero (ZF = 0)

(Nota : questi due salti condizionati sono la stessa cosa hanno soilo nomi 
diversi !!)
                 
JC -  Salta se c'è stato riporto (CF = 1)
JNC - Salta se non c'è stato riporto (CF = 0)

JP - Salta se il Parity flag è 1
JNP - ...

JS - Salata se il Sign flag è 1
JNS - ...

Abbiamo poi due categorie per il confronto tra numeri con segno e senza segno la 
differenza è che nei numeri con segno il bit piu' significativo rappresenta 
appunto il segno del numero(ricordate il complemento a 2)

Con segno:
JG - Salta se il primo è maggiore del secondo
JNG - ...
                        
JGE - salta se il primo è maggiore o uguale del secondo
JNGE - ...

JL - Salta se il primo è minore del secondo
JNL - ...

JLE - Salta se il primo è minore o uguale del secondo
JNLE - ...

Senza segno:
JA - Salta se il primo è piu' grande del secondo
JNA - ...

JB - Salta se il primo è piu' piccolo del secondo
JNB - ...

JAE - ...
JBE - ...

Queste istruzioni di salto condizionato sono indispensabili per il controllo
del flusso del programma per costruire i costrutti if...then e i cicli.
Tornando al nostro programma l'istruzione successiva al salto è 
	
	mov sorgente[si],al

in al c'è il carattere appena letto che deve essere copiato nella variabile 
sorgente, quest'ultima viene trattata a tutti gli effetti come un array di 
caratteri dove si rappresenta l'indice che nell'istruzione successiva viene 
incrementato (inc si) per prepararsi sulla prossima posizione libera.
L'ultima istruzione del ciclo di lettura è  un salto (incodizionato) a 
prossimo_car , in pratica si riprende con la lettura del carattere successivo e 
si prosegue in questo modo fino a che non viene premuto Enter.
Anche se non mi sono soffermato spero abbiate capito come si utilizzano le 
etichette per i salti, basta fare


nome_etichetta:                       ;nota: termina con i due punti
	....
	....
	....
                jmp nome_etichetta    ;nota:non termina con i due punti

Il successivo frammento di programma prepara le due stringhe per il ribaltameto:
		
mov	cx,si
push 	cx	;memorizza la lunghezza nello stack
mov	bx,OFFSET sorgente
mov	si,OFFSET destinaz
add	si,cx	;metto in si la lunghezza della stringa
dec	si	;decrementa si

La prima istruzione mette in cx la lunghezza della stringa che è contenuta 
nell'indice dell'array (si); tale valore viene anche salvato nello stack per 
utilizzarlo in futuro  (push cx).
Con le due istruzioni successive faccio puntare bx all'offset della sorgente e 
si a quello della destinazione e infine sposto si alla fine della stringa 
sorgente per effettuare la copia al contrario (partendo dal  fondo).
L'istruzione add effettua la somma si = si+cx e dec decrementa l'operando (si).

A questo punto viene effettuata la copia ribaltata nella stringa destinazione:

Ribaltamento:
                   mov	  al,[bx]	;routine di ribaltamento
                   mov	  [si],al	; parto dal fondo e la copio
                   inc    bx                   ; in destinaz
                   dec	  si
                   loop	  Ribaltamento	; salto a Ribaltamento

La situazione è questa :
            +--------------- [CX] -------------+
            -------------------------------------
destinaz :  |?|?|?|?|?|?|?|?|?|?|?|?|?|?|?|?|?|
            -------------------------------------
                                              ^
                                              |
                                              si


            -------------------------------------
sorgente:   |c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|
            -------------------------------------
             ^
             |
            bx


si punta alla posizione cx della stringa destinazione, bx punta all'inizio della 
stringa sorgente e in cx c'e la lunghezza della stringa da ribaltare quindi non 
ci resta che scrivere un ciclo di questo tipo (in C):

        while (cx>0)
        {
                destinazione[si] = sorgente[bx];
                bx++;
                si--;
                cx--;
        }
	
che in assembly risulta come sopra.
Notate che lo spostamento da sorgente a destinazione non puo' essere effettuato 
in un unica istruzione perchè il trasferimento memoria-memoria non è consentito 
si deve utilizzare un registro di appoggio (nel nostro caso al).
Inoltre noterete che nel programma assembly manca l'istruzione che decrementa 
cx, questa viene effettuata automaticamente dall'istruzione loop che salta 
all'etichetta indicata dopo aver decrementato cx.

L'ultima parte del programma provvede alla visualizzazione della stringa 
ribaltata :

                pop     si                      ;prelevo la lunghezza
                mov	destinaz[si+1],'$'  	 ;aggiungo il terminatore
                mov	ah,09h		
                mov	dx,OFFSET destinaz
                int     21h                     ;stampo la stringa ribaltata

le prime due istruzioni servono per aggiungere alla fine della stringa il 
terminatore '$' indispensabile per stampare una stringa con la funzione 21h,09h; 
senza questo verrebbero visualizzati strani caratteri (faccine, cuoricini, 
ecc...)

Spero che abbiate capito il funzionamento di questo secondo programma, non che 
sia molto piu' difficile del primo però introduce alcuni controlli di 
fondamentale importanza quali i salti e i cicli indispensabili per scrivere 
programmi che facciano qualcosa !!!


	


** [email protected] **