![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Coordinamento di Francesco Sileno![]() |
di Francesco Sileno
Le MacroTanto per non annoiarvi, vediamo subito come appare il nostro programma ridotto a macro.
.MODEL SMALL
.STACK 2048
stampa MACRO string
mov ah,9
mov dx,OFFSET string
int 21h
ENDM
.DATA
domanda db 'Ciao, quale sarebbe il tuo nome?',0Dh,0Ah,'> $'
nome db 255, 0, 254 DUP (00)
ricamo1 db 0Dh,0Ah,'Così ti chiami $'
ricamo2 db '... bah, pensavo meglio.',0Dh,0Ah,'$'
.CODE
ASSUME CS:@CODE,DS:@DATA
MOV AX,@DATA
MOV DS,AX
stampa domanda
mov ax,0A00h
mov dx,OFFSET nome
int 21h
xor bx,bx
mov bl,[nome+1]
mov [nome+bx+2],'$'
stampa ricamo1
stampa [nome+2]
stampa ricamo2
FINE:
MOV AX,4C00H
INT 21H
END
Quella che appare in alto, prima della definizione dei segmenti, è una
dichiarazione di macro. La sua sintassi è:
Nome_Macro MACRO Par1, Par2, ...
[codice]
ENDM
e può essere fatta ovunque nel sorgente. Meglio però farla in
modo che preceda l'effettiva chiamata, altrimenti si potrebbero generare
dei problemi di referenziamento che dovrete risolvere con compilazioni a
passate multiple.
Per richiamarla è sufficiente:
Nome_Macro P1, P2, ...Il controllo sui parametri viene effettuato in fase di compilazione, in questo esempio effettuare una chiamata a STAMPA AXDOVREBBE causare un errore, in quanto OFFSET AX al massimo potrebbe resituire le coordinate dei transistor che formano il latch che rappresenta il registro AX nella cpu. In realtà ho appena scoperto che il TASM traduce OFFSET AX in AX. Curioso esempio di intelligenza artificiale, pur restando una chiamata priva di senso.
Quello che otteniamo nell'eseguibile non è una chiamata ad una procedura...
in realtà il compilatore sostituisce ad ogni chiamata alla macro il codice
per essa dichiarato, facendosi carico di ricopiare il codice ogni volta.
1 0000 .MODEL SMALL
2 0000 .STACK 2048
3
4 stampa MACRO string
5 mov ah,9
6 mov dx,OFFSET string
7 int 21h
8 ENDM
9
10 0000 .DATA
11 0000 43 69 61 6F 2C 20 71+ domanda db 'Ciao, quale sarebbe +
12 75 61 6C 65 20 73 61+ il tuo nome?',0Dh,0Ah,'> $'
13 72 65 62 62 65 20 69+
14 6C 20 74 75 6F 20 6E+
15 6F 6D 65 3F 0D 0A 3E+
16 20 24
17 0025 FF 00 FE*(00) nome db 255, 0, 254 DUP (00)
18 0125 0D 0A 43 6F 73 8D 20+ ricamo1 db 0Dh,0Ah,'Così ti +
19 74 69 20 63 68 69 61+ chiami $'
20 6D 69 20 24
21 0137 2E 2E 2E 20 62 61 68+ ricamo2 db '... bah, pensavo +
22 2C 20 70 65 6E 73 61+ meglio.',0Dh,0Ah,'$'
23 76 6F 20 6D 65 67 6C+
24 69 6F 2E 0D 0A 24
25
26 0152 .CODE
27 ASSUME CS:@CODE,DS:@DATA
28
29 0000 B8 0000s MOV AX,@DATA
30 0003 8E D8 MOV DS,AX
31
32 stampa domanda
1 33 0005 B4 09 mov ah,9
1 34 0007 BA 0000r mov dx,OFFSET domanda
1 35 000A CD 21 int 21h
36
37 000C B8 0A00 mov ax,0A00h
38 000F BA 0025r mov dx,OFFSET nome
39 0012 CD 21 int 21h
40
41 0014 33 DB xor bx,bx
42 0016 8A 1E 0026r mov bl,[nome+1]
43 001A C6 87 0027r 24 mov [nome+bx+2],'$'
44
45 stampa ricamo1
1 46 001F B4 09 mov ah,9
1 47 0021 BA 0125r mov dx,OFFSET ricamo1
1 48 0024 CD 21 int 21h
49
50 stampa [nome+2]
1 51 0026 B4 09 mov ah,9
1 52 0028 BA 0027r mov dx,OFFSET [nome+2]
1 53 002B CD 21 int 21h
54
55 stampa ricamo2
1 56 002D B4 09 mov ah,9
1 57 002F BA 0137r mov dx,OFFSET ricamo2
1 58 0032 CD 21 int 21h
59
60 0034 FINE:
61 0034 B8 4C00 MOV AX,4C00H
62 0037 CD 21 INT 21H
63
64 END
^^^^^^^^^^
In questa colonna
potete vedere il
codice macchina delle
istruzioni tradotte
Vedete? La dove noi avevamo scritto STAMPA, il compilatore ha sostituito il
codice che costituisce la macro.Notate che anche i parametri vengono sostituiti, ovvero: ad ogni parametro formale dichiarato, viene automaticamente sostituito il parametro effettivo della chiamata. Ed ovviamente se a questo punto si verifica un incongruenza tra tipi di dato esce fuori l'errore. A causa del procedimento di copiatura ( macro espansione ), si deve far attenzione all'uso di etichette all'interno delle macro. Vediamo questo esempio:
.MODEL SMALL
.STACK 2048
Prova MACRO
jmp salto
prova_word dw 0
salto:
ENDM
.DATA
.CODE
ASSUME CS:@CODE,DS:@DATA
MOV AX,@DATA
MOV DS,AX
Prova
Prova
FINE:
MOV AX,4C00H
INT 21H
END
Il suo listing:
1 0000 .MODEL SMALL
2 0000 .STACK 2048
3 LOCALS
4
5 Prova MACRO
6 jmp salto
7 prova_word dw 0
8 salto:
9 ENDM
10
11
12 0000 .DATA
13
14 0000 .CODE
15 ASSUME CS:@CODE,DS:@DATA
16
17 0000 B8 0000s MOV AX,@DATA
18 0003 8E D8 MOV DS,AX
19
20 Prova
1 21 0005 EB 06 jmp salto
1 22 0007 0000 prova_word dw 0
**Error** esmac.asm(21) PROVA(2) Symbol already defined elsewhere: +
PROVA_WORD
1 23 0009 salto:
**Error** esmac.asm(21) PROVA(3) Symbol already defined elsewhere: +
SALTO
24 Prova
1 25 0009 EB 02 jmp salto
1 26 000B 0000 prova_word dw 0
**Error** esmac.asm(22) PROVA(2) Symbol already defined elsewhere: +
PROVA_WORD
1 27 000D salto:
**Error** esmac.asm(22) PROVA(3) Symbol already defined elsewhere: +
SALTO
28
29 000D FINE:
30 000D B8 4C00 MOV AX,4C00H
31 0010 CD 21 INT 21H
32
33 END
Poichè la copiatura avviene in modo esatto, nessuno dice al
compilatore che l'etichetta di una macro viene duplicata se la macro
è chiamata più di una volta.Per ovviare, il metodo più semplice consiste nell'aggiungere la direttiva LOCALS ( nessun punto iniziale! ) all'inizio del sorgente, e dichiarare le etichette in una macro nel formato @@Label, sia per le etichette di salto, che per le variabili.
.MODEL SMALL
.STACK 2048
LOCALS
Prova MACRO
jmp @@salto
@@prova_word dw 0
@@salto:
ENDM
...
In questo modo i riferimenti vengono risolti durante l'espansione della
macro, usando delle etichette incrementali ed univoche.
Le ProcedureQuesta invece è la versione dello stesso programma, che invece della macro usa una procedura:
.MODEL SMALL
.STACK 2048
PAGE ,70
.DATA
domanda db 'Ciao, quale sarebbe il tuo nome?',0Dh,0Ah,'> $'
nome db 255, 0, 254 DUP (00)
ricamo1 db 0Dh,0Ah,'Così ti chiami $'
ricamo2 db '... bah, pensavo meglio.',0Dh,0Ah,'$'
.CODE
ASSUME CS:@CODE,DS:@DATA
MOV AX,@DATA
MOV DS,AX
mov dx,offset domanda
push dx
call stampa
mov ax,0A00h
mov dx,OFFSET nome
int 21h
xor bx,bx
mov bl,[nome+1]
mov [nome+bx+2],'$'
mov dx,offset ricamo1
push dx
call stampa
mov dx,offset nome+2
push dx
call stampa
mov dx,offset ricamo2
push dx
call stampa
FINE:
MOV AX,4C00H
INT 21H
stampa PROC NEAR
pop ax
pop dx ; puntatore a stringa
push ax
mov ah,9
int 21h
ret
stampa ENDP
END
Ora la procedura deve essere scritta all'interno del segmento codice, e si
deve fare attenzione alla posizione in cui la si mette. Poiché in questo
caso non si tratta si una dichiarazione, ma della scrittura di codice
'definitivo', il cui indirizzo di ingresso sarà rappresentato dal nome
stesso della procedura.Se l' avessi definita all'inizio del segmento codice, in fase di esecuzione il suo corpo sarebbe stato eseguito lo stesso all'avvio, nonostante non ci fosse stata alcuna chiamata! Forse un estratto di ipotetico listing del nostro programma con la procedura definita all'inizio del segmento codice vi farà capire meglio:
...
19
20 0152 .CODE
21
22 0000 stampa PROC NEAR
23 0000 58 pop ax
24 0001 5A pop dx ; puntatore a +
25 stringa
26 0002 50 push ax
27 0003 B4 09 mov ah,9
28 0005 CD 21 int 21h
29 0007 C3 ret
30 0008 stampa ENDP
31
32 ASSUME CS:@CODE,DS:@DATA
33
34 0008 B8 0000s MOV AX,@DATA
35 000B 8E D8 MOV DS,AX
36
37 000D BA 0000r mov dx,offset domanda
38 0010 52 push dx
39 0011 E8 FFEC call stampa
...
Avete capito? Il codice è rimasto la dove era scritto, e nessuno dice al
dos che all'inizio c'è una procedura... a meno di non usare un JMP che
scavalca tutte le procedure per arrivare direttamente all'inizio del
programma vero e proprio.
[...]
.CODE
ASSUME CS:@CODE,DS:@DATA
jmp Start_Code
Proc1 PROC NEAR
[...]
Proc1 ENP
Proc2 PROC NEAR
[...]
Proc2 ENP
Start_Code:
MOV AX,@DATA
MOV DS,AX
[...]
Dunque, la dichiarazione ha questa forma:
Nome_Proc PROC [NEAR| FAR]
[codice]
ret
Nome_Proc ENDP
NEAR o FAR indicano se procedura è contenuta nello stesso segmento del
codice, o in un altro. Per ora noi tratteremo solo procedure NEAR.
Quando la cpu trova una CALL, salva sullo stack il registro IP, poi lo
imposta con l'indirizzo della procedura chiamata. All'interno della
procedura, l'istruzione RET effettua un ripristino di IP, prelevandolo
dallo stack, facendo quindi tornare l'esecuzione all'istruzione immediatamente
seguente la CALL. Per passare i parametri alle procedure non Š semplice come per le macro, ma ci sono un paio metodi:
| Free |
+---------+
N | | <- SS:SP
+---------+
N+2 | IP |
+---------+
N+4 | StrPtr |
+---------+
| ... |
Ovviamente IP è l'ultima cosa immessa sullo stack, dopo i nostri
parametri.
Come facciamo ora a prelevare dallo stack i parametri che ci servono, senza
compromettere le informazioni necessarie a tornare indietro?Bene, esaminandolo ( lo stack, alzate lo sguardo! ), vi renderete conto che se si vogliono usare PUSH-POP, dovremo aver cura di salvare il vecchio IP da qualche parte, estrarre tutta la roba che ci serve ( e niente altro! ) , quindi rimettere il vecchio IP a posto. Insomma, qualcosa tipo:
stampa PROC NEAR
pop ax ; vecchio IP
pop bx ; puntatore a stringa
push ax ; ri-salva l'IP di ritorno
mov StringPtr,bx
...
ret
stampa ENDP
Oppure usiamo BP ( non possiamo usare SP! ), lo inizializziamo allo stesso
valore di SP, e lo usiamo come punto di riferimento:
stampa PROC NEAR
push bp
mov bp,sp
mov dx,[bp+4] ; automaticamente si usa il segmento SS
...
ret
stampa ENDP
In questo caso sarebbe preferibile che subito dopo la CALL via sia un
numero di POP pari al numero di PUSH usate per immettere parametri sullo
stack, altrimenti in poco tempo riempirete lo stack di immondizia inutile.
Questo metodo può servire se si devono inviare dei valori di ritorno, in
questo caso li si immette delle posizioni precedentemente occupate dai
parametri di ingresso, senza effettuare troppi push e pop... decidete voi
quale metodo vi ispira maggiormente!
Mischiamole!Io sono molto pigro, a volte mi pesano le dita, e quando devo usare le procedure mi scoccia molto inserire ogni volta tutta la serie di push ed eventuali pop. Per ogni chiamata, devo rifare le stesse operazioni. "Toh!", mi son detto, "ma le macro esistono proprio per questo!".
Stampa MACRO Stringa
push offset Stringa
call P_Stampa
ENDM
...
P_Stampa PROC NEAR
...
ret
P_Stampa ENDP
Adesso è molto più bello, no?
Si, ma insomma?In conclusione, quale usare? Macro o procedure? Dipende. Riassumendo, le caratteristiche distintive sono:
Per gli esseri umani normali, fate un po' come vi viene meglio! Ora vi lascio, devo preparami per la prossima bocciatura. Vedrò se per la prossima volta trovo qualcosa di sfizioso da fare...
esaminatamente,
Cthulhu
|
Copyright © 1996 Beta. Tutti i diritti riservati.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |