![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
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 ENDQuella che appare in alto, prima della definizione dei segmenti, è una dichiarazione di macro. La sua sintassi è: Nome_Macro MACRO Par1, Par2, ... [codice] ENDMe 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 tradotteVedete? 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 ENDIl 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 ENDPoichè 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 ENDOra 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 ENDPNEAR 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 ENDPOppure 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 ENDPIn 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 ENDPAdesso è 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.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |