View Full Version : [Assembler 8086]Stampa caratteri a schermo
Ciao, ho iniziato da poco con questo linguaggio e stavo facendo un programma ( che funziona ) in assembler per sistemi 8086.
Ho scritto questo:
; You are asked to develop an 8086 assembly program that write to console a 10 elements character vector defined
; as a global variable.
; Write operation must be performed through DOS int 21h facility.
DIM EQU 10
.MODEL small
.STACK
.DATA
VETT DB 75,119,98,32,82,111,99,107,115,33 ;Questi sono caratteri ASCCi. Stampa 'Kwb Rocks!'
.CODE
.STARTUP
MOV CX,DIM ;Imposto 10 dentro CX
MOV AH,2 ;Imposto AH a 2, così da poter stampare a schermo i numeri nel vettore
MOV DI,0 ;Imposto 0 dentro DI. DI è un pointer per l'indicizzazione
lab2: MOV DL,VETT[DI] ;Sposto dentro DL l'elemento DIesmo di VETT. DL è usato per restituire l'output a schermo
INC DI ;Aggiornamento del puntatore
INT 21H ;Visualizzo il carattere
DEC CX ;Decremento il contatore
CMP CX,0 ;Confronto CX con 0
JNZ lab2 ;Se CX != da 0 vado a lab2
.EXIT
END
Perchè se definisco VETT come Word e non come Byte l'assembler mi da errore?
Ciao.
Perchè se definisco VETT come Word e non come Byte l'assembler mi da errore?
Puoi indicare l'errore che ti da?
Perchè vorresti mettere word?
Volevo provare a mettere DW perchè a scuola abbiamo sempre messo DW... Tutto qua... Effettivamente devo ancora capire in base a cosa mettere DW, DB, DD o l'altro che ora mi sfugge... Comunque, questo è l'errore:
http://d.imagehost.org/t/0618/erroreasm.jpg (http://d.imagehost.org/view/0618/erroreasm)
E mi sono dato una spiegazione: sul mio libro c'è scritto, a proposito dei registri di dato:
Ciascuno dei quattro registri da 16 bit ouò essere tratto come 2 registri di 8 bit ( il nome dei registri di 8 bit si ottiene sostituendo X con L o H a seconda che si tratti del byte basso o del byte alto, rispettivamente ).
Quindi, siccome io utilizzo il registro AL, di 8 bit ( 1 byte ), abbiam detto, se cerco di farci stare una word dentro ( 16 bit, 2 byte ), è chiaro che non ci sta... C'ho preso?
Una cosa che non ho capito nell'incrementazione dell'indice per un vettore:
mentre in C bastava mettere una costante che definisse il numero di celle del vettore, e una variabile contatore che ad ogni giro veniva incrementata; qua ora devo sia definire una costante che una variabile e ( se ho ben capito ), la prima la decremento e l'altra la aumento, ad ogni giro... E per capire se ho riempito tutto devo fare
CMP <registro_della_costante>, 0
Invece di operare su due dati, non posso aumentarne solo uno e compararlo alla costante?
Ovvero:
DIM EQU 15
MOV CX, DIM
MOV DI, 0 ; DI è l'indice
...
CMP CX, DI ; if ( DI != CX ) torna all'etichetta altrimenti riga sotto
...
Ho appena fatto una prova con questo programmino e pare funzionare ( se ho capito bene come si fa a leggere nei registri e indirizzi di memoria )
; You are asked to develop an 8086 assembly program that reads from console a 10 elements character vector and
; save it in a global variable. Read operation must be performed through DOS int 21h facility.
DIM EQU 10
.MODEL SMALL
.STACK
.DATA
VETT DB DIM DUP (?)
.CODE
.STARTUP
MOV CX,DIM ;Carica in CX la dimensione del vettore. CX è usato per i contatori o operazioni su stringhe
MOV DI,0 ;Azzero DI. DI è un pointer usato per l'indicizzazione
MOV AH,1 ;Imposto AH a 1, questo permette l'acquisizione di un carattere da tastiera.
lab1: INT 21H ;Leggo un carattere da tastiera che viene messo nel registro AL
MOV VETT[DI],AL ;Sposto il carattere dentro VETT[DI]
INC DI ;Incremento DI di uno
;DEC CX ;Decremento CX di uno
CMP CX,DI ;Confronto il valore in CX con quello di DI
JNZ lab1 ;Se diversi vado a lab1
.EXIT
END
E mi sono dato una spiegazione: sul mio libro c'è scritto, a proposito dei registri di dato:
Ciascuno dei quattro registri da 16 bit ouò essere tratto come 2 registri di 8 bit ( il nome dei registri di 8 bit si ottiene sostituendo X con L o H a seconda che si tratti del byte basso o del byte alto, rispettivamente ).
Quindi, siccome io utilizzo il registro AL, di 8 bit ( 1 byte ), abbiam detto, se cerco di farci stare una word dentro ( 16 bit, 2 byte ), è chiaro che non ci sta... C'ho preso?
Uhm, diciamo si e no:
allora, tu in questo caso usi DB perchè vuoi memorizzare dei caratteri, che quindi sono di singolo byte. Se tu avessi voluto memorizzare dei numeri a 16 bit, avresti usato DW, che quindi riservava 2 byte per ogni elemento. Però, allocare 10 elementi DW è come allocare 20 elementi DB.
Visto che per la cpu non c'è nessuna differenza (non sa se sono 20 byte o 10 word), l'assemblatore pensa che tu stia facendo un errore, e ti dice che stai cercando di prendere dei dati che avevi dichiarato come word. Se tu vuoi ad ogni costo dichiarare il vettore come DW, e accedere a singoli byte, puoi modificare così l'istruzione
MOV DL, BYTE PTR VETT[DI]
Che genera lo STESSO codice macchina (perchè, come detto, alla cpu non interessa niente), ma ha l'effetto di dire all'assemblatore: "ok, lo so che sto prendendo byte anche se ho detto word, e voglio fare proprio così".
A questo punto però, devi fare un'altra modifica al listato... vediamo se riesci a trovarla ;)
Una cosa che non ho capito nell'incrementazione dell'indice per un vettore:
Ehm... ;)
mentre in C bastava mettere una costante che definisse il numero di celle del vettore, e una variabile contatore che ad ogni giro veniva incrementata; qua ora devo sia definire una costante che una variabile e ( se ho ben capito ), la prima la decremento e l'altra la aumento, ad ogni giro... E per capire se ho riempito tutto devo fare
CMP <registro_della_costante>, 0
Invece di operare su due dati, non posso aumentarne solo uno e compararlo alla costante?
Si, certo, si può fare tranquillamente. La ragione per cui si fà il contrario è anche un po' storica, per risparmiare istruzioni:
L'8086 ha una istruzione chiamata LOOP che è uguale a:
LOOP etichetta -> CMP CX,0
JNZ etichetta
Per questo si usava (usava, perchè con gli attuali processori paradossalmente è più veloce fare il cmp e il jump rispetto al fare il LOOP) solo quella istruzione in modo da risparmiare qualche byte e velocizzare il programma.
Uhm, diciamo si e no:
allora, tu in questo caso usi DB perchè vuoi memorizzare dei caratteri, che quindi sono di singolo byte. Se tu avessi voluto memorizzare dei numeri a 16 bit, avresti usato DW, che quindi riservava 2 byte per ogni elemento. Però, allocare 10 elementi DW è come allocare 20 elementi DB.
Visto che per la cpu non c'è nessuna differenza (non sa se sono 20 byte o 10 word), l'assemblatore pensa che tu stia facendo un errore, e ti dice che stai cercando di prendere dei dati che avevi dichiarato come word. Se tu vuoi ad ogni costo dichiarare il vettore come DW, e accedere a singoli byte, puoi modificare così l'istruzione
MOV DL, BYTE PTR VETT[DI]
Che genera lo STESSO codice macchina (perchè, come detto, alla cpu non interessa niente), ma ha l'effetto di dire all'assemblatore: "ok, lo so che sto prendendo byte anche se ho detto word, e voglio fare proprio così".
A questo punto però, devi fare un'altra modifica al listato... vediamo se riesci a trovarla ;) Ok, ho provato e avviando il programma succede che mi stampa il carattere più uno spazio. Questo perchè io vado ad occupare solo 1 dei 2 byte disponibili per carattere, però allo stesso tempo, quando incremento, gli dico di passare byte per byte e non di saltare di due in due.
È bastato cambiare l'istruzione
INC DI
con
ADD DI,2
E torna a stampare corretto.
Ehm... ;) Incremento volevo dire :D
Si, certo, si può fare tranquillamente. La ragione per cui si fà il contrario è anche un po' storica, per risparmiare istruzioni:
L'8086 ha una istruzione chiamata LOOP che è uguale a:
LOOP etichetta -> CMP CX,0
JNZ etichetta
Per questo si usava (usava, perchè con gli attuali processori paradossalmente è più veloce fare il cmp e il jump rispetto al fare il LOOP) solo quella istruzione in modo da risparmiare qualche byte e velocizzare il programma.
Mha... Sarà... Io trovo molto più logico il mio ragionamento ( sarà perchè in C si fa così? )... Ora chiederò al professore se non gli scoccia se uso questo metodo... Visto che sto studiando due linguaggi di programmazione se posso semplificarmi le cose e rendere l'approccio al problema tramite strutture simile è meglio, almeno mi devo ricordare meno cose.
EDIT: Bellissimo il thread su WinUX, mi hai fatto fare due risate :rotfl:
Ok, ho provato e avviando il programma succede che mi stampa il carattere più uno spazio. Questo perchè io vado ad occupare solo 1 dei 2 byte disponibili per carattere, però allo stesso tempo, quando incremento, gli dico di passare byte per byte e non di saltare di due in due.
È bastato cambiare l'istruzione
INC DI
con
ADD DI,2
E torna a stampare corretto.
Esatto! è proprio così.
Mha... Sarà... Io trovo molto più logico il mio ragionamento ( sarà perchè in C si fa così? )... Ora chiederò al professore se non gli scoccia se uso questo metodo... Visto che sto studiando due linguaggi di programmazione se posso semplificarmi le cose e rendere l'approccio al problema tramite strutture simile è meglio, almeno mi devo ricordare meno cose.
Si si il tuo ragionamento è giusto, non c'è niente di sbagliato, nessuno di obbliga a contare fino a 0 invece di comparare di volta in volta. Come ti dicevo, è solo una questione "storica", quando i limiti dell'hardware erano notevoli, e risparmiare qualche byte o qualche ciclo di clock faceva la differenza. Al giorno d'oggi con le nostre macchine superpotenti, con processori con sistemi di esecuzione avanzati e memorie enormi, questi problemi sono sempre meno rilevanti.
Anche qualsiasi compilatore C usa il tuo metodo, se compili questo codice inutile:
int main()
{
int a, c;
for(c=0; c<10; ++c)
a = c;
return 0;
}
(ho usato la GCC con la riga di comando:
gcc prova.c -S -O0 -masm=intel -m32)
viene tradotto in questo assembly:
main:
push ebp ; prepara lo stack e alloca le 2 variabili
mov ebp, esp
sub esp, 16
mov DWORD PTR [ebp-8], 0
jmp .L2
ciclo:
mov eax, DWORD PTR [ebp-8] ; prende C
mov DWORD PTR [ebp-4], eax ; lo salva in a
add DWORD PTR [ebp-8], 1 ; ++c
.L2:
cmp DWORD PTR [ebp-8], 9 ; confronta C con 9
jle ciclo ; salta se è minore o uguale
mov eax, 0 ; return 0
leave
ret
Come puoi vedere, usa la tua stessa idea (non ti preoccupare di quei DWORD PTR ...).
Si si il tuo ragionamento è giusto, non c'è niente di sbagliato, nessuno di obbliga a contare fino a 0 invece di comparare di volta in volta. Come ti dicevo, è solo una questione "storica", quando i limiti dell'hardware erano notevoli, e risparmiare qualche byte o qualche ciclo di clock faceva la differenza. Al giorno d'oggi con le nostre macchine superpotenti, con processori con sistemi di esecuzione avanzati e memorie enormi, questi problemi sono sempre meno rilevanti.
Anche qualsiasi compilatore C usa il tuo metodo, se compili questo codice inutile:
int main()
{
int a, c;
for(c=0; c<10; ++c)
a = c;
return 0;
}
(ho usato la GCC con la riga di comando:
gcc prova.c -S -O0 -masm=intel -m32)
viene tradotto in questo assembly:
main:
push ebp ; prepara lo stack e alloca le 2 variabili
mov ebp, esp
sub esp, 16
mov DWORD PTR [ebp-8], 0
jmp .L2
ciclo:
mov eax, DWORD PTR [ebp-8] ; prende C
mov DWORD PTR [ebp-4], eax ; lo salva in a
add DWORD PTR [ebp-8], 1 ; ++c
.L2:
cmp DWORD PTR [ebp-8], 9 ; confronta C con 9
jle ciclo ; salta se è minore o uguale
mov eax, 0 ; return 0
leave
ret
Come puoi vedere, usa la tua stessa idea (non ti preoccupare di quei DWORD PTR ...).
No ma fammi capire... GCC è in grado di generare codice assembly da codice C? :eek: :eek:
I commenti li hai aggiunti tu immagino :D
No ma fammi capire... GCC è in grado di generare codice assembly da codice C? :eek: :eek:
Ehm... è proprio quello che ci si aspetta da un compilatore :D
Il ciclo di compilazione è:
sorgenteC ---compilatore--> sorgenteASM ---assembler--> codiceOggetto ---linker--> programmaEseguibile
In effetti quando chiami Gcc lui da solo poi richiama l'assemblatore e il linker automaticamente.
Ci sono poi anche compilatori che traducono direttamente in codice macchina, ma non so dirti quali siano.
I commenti li hai aggiunti tu immagino :D
Sarebbe chiedere troppo :asd:
Ehm... è proprio quello che ci si aspetta da un compilatore :D
Il ciclo di compilazione è:
sorgenteC ---compilatore--> sorgenteASM ---assembler--> codiceOggetto ---linker--> programmaEseguibile
In effetti quando chiami Gcc lui da solo poi richiama l'assemblatore e il linker automaticamente.
Ci sono poi anche compilatori che traducono direttamente in codice macchina, ma non so dirti quali siano.
Sarebbe chiedere troppo :asd:
Beh io credevo che GCC traducesse direttamente in codice macchina... O meglio che GCC fosse l'insieme di compilatore, linker e assembler... ( lo so che GCC sta per Gnu C Compiler :D ) ecco perchè non mi spiegavo questo...
Beh io credevo che GCC traducesse direttamente in codice macchina... O meglio che GCC fosse l'insieme di compilatore, linker e assembler... ( lo so che GCC sta per Gnu C Compiler :D ) ecco perchè non mi spiegavo questo...
Non è più vero neanche questo :D
http://it.wikipedia.org/wiki/GNU_Compiler_Collection
Quando chiami solo "gcc" dalla linea di comando, non chiami neanche più il compilatore vero e proprio, ma un front-end che richiama poi il compilatore, assembler e linker... per il linguaggio giusto in base al sorgente che gli passi come parametro (almeno per i sorgenti con un'estensione identificabile).
Infatti se vuoi puoi passare a gcc il nome di un file Assembly, e lui provvede da solo a chiamare solo l'assemblatore e il linker, o puoi passargli dei file oggetto e lui chiama solo il linker, oppure gli puoi passare tutti e tre i tipi contemporaneamente e lui si preoccupa di attivare le fasi giuste in base al file.
Non è più vero neanche questo :D
http://it.wikipedia.org/wiki/GNU_Compiler_Collection
Quando chiami solo "gcc" dalla linea di comando, non chiami neanche più il compilatore vero e proprio, ma un front-end che richiama poi il compilatore, assembler e linker... per il linguaggio giusto in base al sorgente che gli passi come parametro (almeno per i sorgenti con un'estensione identificabile).
Infatti se vuoi puoi passare a gcc il nome di un file Assembly, e lui provvede da solo a chiamare solo l'assemblatore e il linker, o puoi passargli dei file oggetto e lui chiama solo il linker, oppure gli puoi passare tutti e tre i tipi contemporaneamente e lui si preoccupa di attivare le fasi giuste in base al file.
Ah ok, capito grazie! :)
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.