Cerchiamo di chiarire in cosa consista la differenza tra MACRO e SUBROUTINE.
Macro
Una MACRO equivale a una o più righe di codice che vengono raccolte
sotto una unica label.
Queste righe possono essere composte da qualsiasi codice, operazione o
comando ammesso dal linguaggio.
Il meccanismo di funzionamento è semplicissimo:
Ogni volta che il compilatore incontrerà la label definita come MACRO, la
sostituirà con le righe sottointese.
Quindi una MACRO non è altro che l' aver assegnato una label non ad una
costante o variabile o a un registro, ma a un gruppo più o meno complesso di
linee di codice.
Facciamo un esempio con il Macro Assembler MPASM, dove la sintassi per la MACRO è:
label macro
<parametri>
<codice>
endm |
La MACRO deve essere associata ad una label,
a cui fa seguito la direttiva macro, che può essere seguita da un certo numero di parametri.
La MACRO termina con la direttiva endm,
che la chiude.
Quanto scritto tra macro e endm
costituisce il corpo della MACRO stessa.
Esemplifichiamo ulteriormente: una semplice MACRO può essere
la seguente
Wait3Cycle macro
nop
nop
nop
endm |
Ogni volta che nel listato sorgente sarà richiamata la label Wait3Cycle
, il compilatore la sostituirà con il codice relativo.
Sorgente |
Lista
compilata |
Wait3Cycle
bsf Output1
Wait3Cycle
bsf Output2
Wait3Cycle
Wait3Cycle |
nop
; macro espansa
nop
nop
bsf Output1
nop
; macro espansa
nop
nop
bsf Output2
nop
; macro espansa
nop
nop
nop
; macro espansa
nop
nop |
Da osservare che nel sorgente la richiesta della MACRO è
costituita semplicemente dalla sua label (e dagli eventuali parametri): una
volta che è stata definita una MACRO, la sua label può essere utilizzata nel
programma.
Quando l'Assembler la incontra, la sostituisce con la
sequenza di istruzioni sottintese e le elabora come se esse fossero scritte a
quel punto del programma.
La MACRO può avere dei parametri e può avere ogni genere di
complessità, secondo i limiti del compilatore:
;**********************************************************
; LCDlpos - Move cursor to a specified line position
; Use : LCDpos line_num, pos
;**********************************************************
LCDLPOS macro line_num, pos
VARIABLE posline
IF (posiz > LCDCHRXLINE)
ERROR "Position over range in
LCDlpos"
ENDIF
IF (line_num == 1)
posline = (LCDL1 + posiz) ; on first line
ELSE
IF (line_num == 2)
posline = (LCDL2 + posiz) ; on second line
ELSE
IF (line_num == 3)
posline = (LCDL3 + posiz) ; on third line
ELSE
IF (line_num == 4)
posline = (LCDL4 + posiz) ; on fourth line
ELSE
ERROR "Line number over range in
LCDlpos"
ENDIF
ENDIF
ENDIF
ENDIF
LCDDDADR posline
endm |
I parametri della MACRO possono essere a loro volta label e la
MACRO (in questo esempio all' ultima riga) può
chiamare altre MACRO (in questo caso LCDADDR
con il parametro posline), ma può
richiamare anche SUBROUTINES.
Quali sono i vantaggi di usare una MACRO?
Essenzialmente che:
-
la MACRO rendere più veloce e più facile scrivere un
tratto di codice che viene ripetuto in un programma: al posto della
ripetizione delle linee di codice, si richiama una sola label; il
compilatore penserà poi ad espanderle.
-
La MACRO può essere scritta e provata a parte per
eseguire una data funzione. Nel caso in cui occorra una revisione del
codice, basterà agire in un punto solo, ovvero nella definizione,
lasciando invariato il resto del sorgente.
Così, anche una struttura che è identica per la gestione di diversi
processi e da origine ad un listato di base comune, MACRO ad hoc per ogni
specifica funzione vanno utilizzate per individualizzare ogni processo.
-
La MACRO rende il listato sorgente più facilmente
leggibile: una label auto esplicativa fa la vece ci un gruppo di righe di
codice.
Per contro una MACRO:
-
nasconde ciò che sta facendo il codice e la dimensione
del codice stesso. Questo può essere negativo se si sta tentando di
scrivere codice il più possibile compatto per limiti di dimensione
globale del programma.
-
In particolare, la MACRO, ogni volta che viene espansa,
crea nel codice un numero di righe pari a quelle della sua
definizione. Quanto più la MACRO è ripetuta, tanto più saranno le
ripetizioni e, di conseguenza, l'
occupazione di spazio nella memoria programma.
-
Certamente è difficile ricordare in che cosa consisteva
la MACRO xyz scritta tempo fa. Occorre che al momento della scrittura ne
sia stata stesa una documentazione esauriente e funzionale.
E, cosa importantissima da ricordare:
- Una MACRO va definita PRIMA di essere
utilizzata.
Quindi nel sorgente le
definizioni o i link alle librerie MACRO dovranno essere tipicamente poste
prima dell' inizio del programma vero e proprio, o comunque prima
dell' uso della MACRO stessa.
|
Questa ultima considerazione, se non chiara, va meditata un
attimo fino a capirne il significato, perchè in questa è sottintesa la
principale differenza tra MACRO e SUBROUTINE. Ovvero la MACRO è definita al
di fuori del programma vero e proprio, dato che non esiste come
istruzioni nel programma se
non quando viene inserita la sua label ed è il compilatore che esegue la
trasformazione della label nella lista e poi nei codici binari del file
.hex.
Subroutine
La SUBROUTINE è un tratto di programma che, similmente alla
MACRO, raccoglie più linee di codice sotto una sola label.
Questo tratto di programma, che può essere piazzato dove si
preferisce, costituisce un sotto-programma di quello principale, a cui si
accede indirizzando opportunamente il Program Counter.
La differenza con la MACRO, dunqu, è la seguente:
-
La MACRO esiste solo come definizione nel listato sorgente
(o in una libreria raccolta con un include).
Se non è nominata in una riga della parte eseguibile del sorgente, non
viene mai espansa e non occupa alcuno spazio nella memoria programma.
Se viene richiamata, però, ogni volta occupa un numero di righe di codice
pari a quelle della sua definizione ed un corrispondente numero di bytes
nella memoria programma.
-
La SUBROUTINE deve essere scritta in un punto del
programma e, compilata, occupa il necessario numero di byte della memoria
programma.
Ma ogni volta che viene richiamata il compilatore non ripete la lista
delle istruzioni, ma rinvia il Program Counter alla locazione di inizio
della SUBROUTINE.
Ovviamente anche le subrotines possono essere raccolte in librerie,
linkate con include.
E la differenza fondamentale nell' uso è che:
-
la MACRO si richiama
per la compilazione semplicemente inserendo label e parametri.
La fine della
macro è semplicemente la fina delle istruzioni espanse.
-
La SUBROUTINE si richiama all' esecuzione, attraverso un
salto, ovvero ad una
modifica del Program Counter, effettuata dall' istruzione call o
rcall. La
fine della SUBROUTINE deve essere data dall' istruzione return che riporta il
Program Counter nella posizione di partenza.
La sintassi della SUBROUTINE sarà:
label call
subroutine1
...
subroutine1
....
return |
Questo tratto di programma verrà compilato e posizionato all'
indirizzo corrispondente alla sua posizione logica nel listato sorgente. Non
occorre che la SUBROUTINE sia definita prima del suo impiego, dato che non è
un blocco sottinteso da una label come la MACRO, ma una area di istruzioni
presenti nella memoria programma.
Quindi, definendo una subroutine che faccia una azione analoga
alla MACRO prima esemplificata, basterà scrivere le stesse istruzioni, ma
terminandole con un return per riposizionare il Program Counter; non serve
alcuna direttiva ulteriore.
Wait3Cycle nop
nop
nop
return |
ogni volta che nel listato sorgente sarà richiamata la label
con un call, in quel punto il compilatore creerà solamente i codici
binari per la chiamata all' indirizzo corrispondente alla label:
Sorgente |
Lista
compilata |
call
Wait3Cycle
bsf Output1
call Wait3Cycle
bsf Output2
call
Wait3Cycle
call
Wait3Cycle |
call
Wait3Cycle ;
chiamata subroutine
bsf Output1
call Wait3Cycle
; chiamata subroutine
bsf Output2
call Wait3Cycle
; chiamata subroutine
call Wait3Cycle
; chiamata subroutine |
Come per la MACRO, anche la SUBROUTINE può essere creata con
qualsiasi funzione o codice o operazione ammessi dal linguaggio e può
contenere chiamate ad altre subroutine (nexted o annidate), assumendo quindi
qualsiasi grado di complessità.
Cosa c'è di positivo nell' uso delle SUBROUTINES?
-
la SUBROUTINE rendere più veloce e più facile scrivere
un tratto di codice che viene ripetuto in un programma: al posto della
ripetizione delle linee di codice, si richiama una sola label; il
compilatore penserà a creare i codici per la chiamata, ovvero lo
spostamento del PC, e per il suo riposizionamento.
-
La SUBROUTINE può essere scritta e provata a parte per
eseguire una data funzione. Nel caso in cui occorra una revisione del
codice, basterà agire in un punto solo, ovvero nella definizione,
lasciando invariato il resto del sorgente.
Così, anche una struttura che è identica per la gestione di diversi
processi da origine ad un listato comune, dove SUBROUTINE ad hoc per ogni
specifica funzione vanno utilizzate per ogni processo.
-
La SUBROUTINE rende il listato sorgente più facilmente
leggibile: una label auto esplicativa fa la vece ci un gruppo di righe di
codice.
Volutamente è stata mantenuta la stessa dizione usata per le
MACRO. A questo punto parrebbe che le cose siano identiche. Ma ci sono
differenze:
Il concetto importante è che, ripetiamolo ancora:
-
la MACRO è definita al di fuori del programma vero e
proprio,
dato che non esiste nel programma stesso se non quando viene inserita la sua
label e il compilatore esegue la trasformazione della lista in codici binari
del file .hex.
Per utilizzarla non occorre alcuna istruzione, dato che al posto della
label il compilatore ha inserito i codici operativi, che, quindi, saranno
eseguiti di seguito come parte integrante di quel tratto di programma.
-
La SUBROUTINE è una parte del programma. Deve essere scritta nel
programma e il suo accesso è effettuato dalle istruzioni di chiamata (call).
L' accesso alla subroutine, quindi, non è la sostituzione della label con
le righe di codice sottintese, ma è una deviazione del Program Counter
verso la posizione dove si trova la subroutine stessa. Questa deviazione o
salto è prodotta variando il contenuto del PC attraverso le istruzioni di
chiamata (call), che devono avere come oggetto la label che definisce la
subroutine.
|
Il ritorno all' esecuzione del flusso che si era abbandonato è ancora una
modifica del PC effettuato dalla istruzione return.
Ovviamente, se nessuna istruzione di chiamata è presente nella lista, i
codici della subroutine sono comunque compilati nel programma.
Per chiarire ancora meglio, l' azione della subroutine è come se, facendo qualcosa,
dovessimo interromper per rispondere al telefono, per poi tornare al lavoro
una volta esaurita la telefonata.
Contro la
SUBROUTINE si deve dire che
-
Ci sono processori dove lo stack è molto piccolo, ad
esempio solo 2 livelli (PIC10F). In questo caso il nexting di subroutines è
inapplicabile e bisogna fare attenzione a come si stanno usando, per
evitare un crash dello stack.
-
La SUBROUTINE va chiamata, ovvero richiede una istruzione
di call o rcall
e il relativo tempo di esecuzione.
Anche la sua fine richiede una istruzione di return ed il relativo tempo
di esecuzione
L' esempio prima fatto del Wait3Cycle
esemplifica bene questa ultima considerazione.
Per la MACRO la tabella elenca i cicli impegnati nell' esecuzione del codice e
la quantità di bytes impegnati nella memoria programma:
Sorgente |
Listato finale |
cicli |
bytes |
Wait3Cycle
bsf Output1
Wait3Cycle
bsf Output2
Wait3Cycle
Wait3Cycle |
nop
nop
nop
bsf Output1
nop
nop
nop
bsf Output2
nop
nop
nop
nop
nop
nop |
1
1
1
1
1
1
1
1
1
1
1
1
1
1 |
1
1
1
1
1
1
1
1
1
1
1
1
1
1 |
|
TOT. |
14 |
14 |
Anche per la SUBROUTINE la tabella elenca i cicli
impegnati nell' esecuzione del codice e la quantità di bytes impieganti nella
memoria programma:
Sorgente |
Listato finale |
cicli |
bytes |
call Wait3Cycle
bsf Output1
call Wait3Cycle
bsf Output2
call Wait3Cycle
call Wait3Cycle
Wait3Cycle nop
nop
nop
return |
call Wait3Cycle
bsf Output1
call Wait3Cycle
bsf Output2
call Wait3Cycle
call Wait3Cycle
Wait3Cycle nop
nop
nop
return |
2+5
1
2+5
1
2+5
2+5
1
1
1
2 |
1
1
1
1
1
1
1
1
1
1 |
|
TOT. |
35 |
10 |
Si vede come la MACRO occupi meno tempo in esecuzione e più
spazio in memoria della SUBROUTINE e viceversa.
Certamente nell' esempio la differenza è poca cosa, ma pensiamo a un
programma reale dove macro o subroutine siano richiamate decine o centinaia di
volte e siano di dimensioni diverse dai tre nop elencati.
Peraltro, la durata di Wait3Cycle
come macro è di 3 cicli, ma come subroutine è di 7 cicli
dato che va sommato il tempo delle istruzioni call
e return.
|
Un esempio noto lo possiamo vedere sul sito del Delay
code generator, di cui abbiamo trattato
qui:
- la tabella di impostazione dei dati consente di scegliere se si
desidera una subroutine o un tratto di codice "piano"
eventualmente utilizzabile come macro.
Nel codice generato per la subroutine viene conteggiata l'
esecuzione delle istruzioni call e return
in modo da ottenere un ritardo esatto.
Vediamo il risultato della casella non barrata (macro) e della
casella barrata (subroutine): il numero di cicli del loop della prima
è 1000 e quello della seconda è 996 + 4 cicli dovuti a call e
return. |
Delaymacro macro ;998 cycles
movlw 0xC7
movwf d1
movlw 0x01
movwf d2
Delaymacro_0
decfsz d1, f
goto $+2
decfsz d2, f
goto Delaymacro_0
;2 cycles
goto $+1 ; 998 + 2 = 1000
endm
|
Delaysub ;993 cycles
movlw 0xC6
movwf d1
movlw 0x01
movwf d2
Delaysub_0
decfsz d1, f
goto $+2
decfsz d2, f
goto Delaysub_0
goto $+1 ;3 cycles
nop ;4 cycles (including call)
return
|
Una nota importante:
Non va confuso il numero dei cicli che la funzione esegue con il
numero delle linee di codice o con il loro impiego di memoria programma.
|
Ad esempio la MACRO Delaymacro occupa 9 linee di codice con un impegno di 12 bytes.
La SUBROUTINE Delaysub occupa 11 linee (più quella della call) con un impegno di 15
bytes (più i due del call).
Infatti la chiamata alla subroutine costerà in memoria programma il codice
equivalente alla riga:
ovvero 2 byte e il tempo necessario alla sua esecuzione (2 cicli).
Ogni volta che nel sorgente sarà specificato:
non sarà utilizzato alcun ciclo extra di esecuzione, ma saranno impegnati
12 bytes nella memoria programma.
Ma il tempo di
ritardo generato sarà esattamente identico per entrambe le soluzioni !!!.
Dunque quale scegliere? In questo caso è abbastanza semplice: tanto
maggiore è il numero delle volte che il programma richiama la funzione, tanto
meno la macro (la cui ripetizione occupa più memoria) sarà
conveniente. Ad esempio, per ottenere 3000 cicli di attesa, effettuiamo la
chiamata per tre volte dell' attesa di 1000 cicli con la subroutine e
confrontiamo il risultato della compilazione con l' identico tempo di 3000
cicli ottenuto citando tre volte la macro:
Subroutine
per un ritardo di 3000 cicli |
|
Macro
per un ritrado di 3000 cicli |
Sorgente |
Lista
compilata |
|
Sorgente |
Lista
compilata |
....
call
Delaysub
call
Delaysub
call Delaysub
....
Delaysub
movlw 0xC6
movwf d1
movlw 0x01
movwf d2
Dels_0
decfsz d1, f
goto $+2
decfsz d2, f
goto Dels_0
goto $+1
nop
return
|
....
call
Delaysub
call
Delaysub
call Delaysub
....
Delaysub
movlw 0xC6
movwf d1
movlw 0x01
movwf d2
Dels_0
decfsz d1, f
goto $+2
decfsz d2, f
goto Dels_0
goto $+1
nop
return
|
|
Delaymacro
macro
movlw 0xC7
movwf d1
movlw 0x01
movwf d2
Delm_0
decfsz d1, f
goto $+2
decfsz d2, f
goto Delm_0
goto $+1
endm
....
Delaymacro
Delaymacro
Delaymacro
.... |
movlw 0xC7
movwf d1
movlw 0x01
movwf d2
Delm_0
decfsz d1, f
goto $+2
decfsz d2, f
goto Delm_0
goto $+1
movlw 0xC7
movwf d1
movlw 0x01
movwf d2
Delm_01
decfsz d1, f
goto $+2
decfsz d2, f
goto Delm_01
goto $+1
movlw 0xC7
movwf d1
movlw 0x01
movwf d2
Delm_02
decfsz d1, f
goto $+2
decfsz d2, f
goto Delm_02
goto $+1 |
totale |
21
bytes |
|
totale |
35 bytes |
Per la MACRO, occorre definirla PRIMA di utilizzarla, mentre la
subroutine può essere piazzata dove meglio si desidera all' interno del
sorgente; in ogni caso occorre definire le label. Quindi, rispetto al numero di linee occupate nel sorgente, tra MACRO
e SUBROUTINE non c'è quasi differenza.
La differenza si crea dopo la compilazione: la definizione della
SUBROUTINE fa parte del programma e occupa memoria programma, come le tre
chiamate call. Ma ogni call
non fa che rimandare il Program Counter all' inizio del tratto di programma
che ha come label Delaysub e non aggiunge niente al listato.
Nel caso della MACRO, invece, la definizione della macro non occupa
spazio in memoria programma, ma ogni volta che la label Delaymacro è
richiamata il compilatore ricopia TUTTE le istruzioni che la compongono,
occupando memoria programma corrispondente.
Dunque, una reiterazione dell' uso di una macro di una certa dimensione,
rispetto ad una subroutine che faccia le stesse funzioni, è fortemente
penalizzante sull' occupazione della memoria.
Anche per la SUBROUTINE non va dimenticato che, come per la MACRO:
-
la chiamata alla SUBROUTINE nasconde ciò che essa sta
facendo, il codice e la dimensione
del codice stesso. Questo può essere negativo se si sta tentando di
scrivere codice il più possibile compatto per limiti di dimensione
globale del programma.
-
Certamente è difficile ricordare in che cosa consisteva
la SUBROUTINE xyz scritta tempo fa. Occorre che al momento della scrittura ne
sia stata stesa una documentazione esauriente e funzionale.
In più:
-
come si è visto, per le macro gli argomenti di input e
output sono definibili nella creazione della MACRO
Per quanto riguarda le SUBROUTINE non è possibile definire argomenti
all'interno della chiamata della subroutine. Però, variabili predefinite
nel programma principale possono essere utilizzate come argomenti.
Una sequenza logica è questa: definire le variabili, chiamare la
subroutine che le utilizza e quindi ri aggiornare le variabili,
se necessario.
Subroutine |
Macro |
;
Somma due numeri a 16 bit
CBLOCK 0x20
ARG1H ;argomento1 byte alto
ARG1L ;argomento1 byte basso
ARG2H ;argomento2 byte alto
ARG2L ;argomento2 byte basso
RESH ;risultato byte alto
RESL ;risultato byte basso
ENDC
ORG 0x00 ;Reset vector
Start ; carica variabili
movlw arg1h
movwf ARG1H
movlw arg1l
movwf ARG1L
movlw arg2h
movwf ARG2H
movlw arg2l
movwf ARG2L
call ADD16
; chiama subroutine
locked bra locked
; blocco
; subroutine di somma
ADD16 clrf RESH
movf
ARG1L, w
addwf ARG2L,w
movwf RESL
btfsc STATUS,C
incf
RESH,f
movf
ARG1H,W
addwf ARG2H,w
addwf RESH,f
return |
;
Somma due numeri a 16 bit
CBLOCK 0x20
ARG1H ;argomento1 byte alto
ARG1L ;argomento1 byte basso
ARG2H ;argomento2 byte alto
ARG2L ;argomento2 byte basso
RESH ;risultato byte alto
RESL ;risultato byte basso
ENDC
; definizione macro
ADD16 macro ARG1H, ARG1L,
ARG2H, ARG2L
clrf
RESH
movlw
ARG1L
addlw ARG2L,w
movwf RESL
btfsc STATUS,C
incf
RESH,f
movlw
ARG1H
addlw ARG2H,w
addlw RESH,f
endm
ORG 0x00 ;Reset vector
Start ; richiama macro
ADD16 arg1h, arg1l, arg1h, arg2l
locked bra locked
; blocco
|
Nell' esempio la sub ha i parametri in locazioni RAM, la macro usa
parametri diretti: sono soluzioni diverse in vista di applicazioni
diverse.
Si può anche ricorrere alla tecnica di usare una MACRO che richiama una
SUBROUTINE a cui passa parametri.
Ad esempio, volendo passare il parametro parametro
possiamo utilizzare la macro scrivi
che genera lo stesso codice che si avrebbe scrivendo la sola subroutine scrivi,
ma risulta più chiaro alla lettura del sorgente.
Subroutine |
Macro + Subroutine |
;
uso
movlw parametro
call scrivi
....
; definizione subroutine
scrivi:
movwf destinazione
....
....
return |
; definizione macro
scrivi macro parametro
movlw parametro
call
subroutine
endm
....
; uso
scrivi parametro
....
; definizione subroutine
subroutine
movwf destinazione
....
....
return |
L' uso di mnemonici opportuni ed una documentazione chiara rendono queste
operazioni molto funzionali.
In conclusione ?
Per prima cosa possiamo affermare che:
sia MACRO che SUBROUTINE sono elementi indispensabili per una buona
programmazione:
non se ne può fare a meno.
Inoltre sia le une che le altre fanno si che il codice sorgente diventi più
flessibile, funzionale e leggibile.
|
In sostanza
- le SUBROUTINE possono far risparmiare memoria programma, ma
richiedono più tempo di esecuzione a causa dei call e
return. Inoltre è
più complesso passare parametri che non alle MACRO.
-
Le MACRO riducono il tempo di esecuzione del programma, ma appesantiscono la
memoria programma. Però è più semplice passare parametri (che fanno
parte della definizione della macro stessa).
L' uso di una o dell' altra sono quindi lasciate al giudizio
del programmatore, che valuterà dove è il caso di risparmiare tempo o
spazio.
In generale si potrà dire questo:
- Se la funzione da richiamare origina molte linee di
codice ed è richiamata più volte, probabilmente è meglio una
SUBROUTINE che una MACRO, la quale darebbe origine ad una occupazione
massiccia della memoria.
Tra l' altro, una funzione complessa richiederà un tempo di esecuzione al
cui confronto il tempo aggiunto da call e return saranno poco significativi.
- Se la funzione da richiamare è composta da poche linee,
allora probabilmente è più pratica la MACRO, anche se ripetuta
più volte.
Se il codice è breve, anche molte ripetizioni non peseranno
troppo sull' occupazione di memoria, mentre un codice di esecuzione breve
sarebbe penalizzato significativamente da call e return richiesti dalla
SUBROUTINE.
|
In particolare sono comuni librerie di macro per i processori
RISC, come i PIC, con lo scopo di creare opcodes che raccolgono più di una
istruzione base. Ad esempio, la creazione di macro come le seguenti rendono la
programmazione più semplice:
Definizione subroutine
|
Uso
|
Impegno
|
sknc
macro
; Skip if Carry Not Set
btfsc STATUS, C
endm
|
sknc
|
1
byte / 1 ciclo
|
jmpc macro destination ;
jump if Carry Set
btfsc STATUS, C
goto destination
endm
|
jmpc
destination
|
1
(3) byte
1 (3) cicli
|
Bank0 MACRO ;
Set Bank 0
bcf STATUS, RP0
bcf STATUS, RP1
ENDM
|
Bank0
|
2
bytes / 2 cicli
|
Qui, la funzione pratica della MACRO è quella di
"estendere" il set delle istruzioni disponibili con ulteriori
comodi codici mnemonici. In
casi del genere è interessante osservare come le caratteristiche di spazio
occupato in memoria tra macro e sub siano praticamente invertite, sopratutto
per molte ripetizioni.
Infatti, certamente sarebbe possibile utilizzare anche subroutines per
ottenere lo stesso risultato, ma la
struttura del call-return per
codici così corti non sarebbe
conveniente, dilatando i tempi di esecuzione anche di 4 volte.
Inoltre va notato che si avrebbe l' opposto di quello che capita normalmente,
ovvero, con l'uso di subroutines ci sarebbe un aumento dello spazio
richiesto in memoria e non una riduzione.
Definizione
subroutine
|
Uso
|
Impegno
|
sknc
; Skip if Carry Not Set
btfsc STATUS, C
return
|
call
snkc
|
1 + 2
bytes / 1 + 4 cicli
|
Bank0
;
Set Bank 0
bcf STATUS, RP0
bcf STATUS, RP1
return
|
call
Bank0
|
2 + 2
bytes / 2 + 4 cicli
|
Da osservare che la macro jmpc, che ha un parametro
destination, non è esprimibile in questa forma come subroutine; occorre
passare il parametro in altri modi, ad esempio attraverso l' accumulatore, lo
stack o locazioni di RAM. Caso particolare, anche per una funzione complessa, ma che è richiamata
una sola o poche volte, la MACRO può essere preferibile, in
quanto raccoglie nel sorgete la funzione sotto una semplice label e
permette un rapido passaggio di parametri.
Non
essendo ripetuta troppe volte, anche una grossa porzione di codice
probabilmente può generare pochi problemi di eccessiva occupazione di
memoria.
In ogni caso va compreso che:
funzioni ricorsive
in un
programma vanno per forza di cose trasformate in blocchi logici, vuoi MACRO o
SUBROUTINE.
|
Un blocco logico che svolge una determinata
funzione è come un prefabbricato in una costruzione: già pronto, basta
inserirlo così come sta nel sorgente. Sapendo cosa fa e come, possiamo usare
questi blocchi che semplificano la scrittura del programma, rendo leggibile il
sorgente e, se raccolti in librerie, sono disponibili per essere utilizzati
non solo nel programma in scrittura, ma anche in ogni altro programma simile,
facendo risparmiare tempo e sforzi.
Quanto detto non vale solo per l'Assembly che è stato usato per gli
esempi, dato che praticamente tutti i linguaggi hanno la possibilità di
creare MACRO e SUBROUTINE.
|
Ad esempio, in C, una macro è una
label (identificatore) definita all' interno di una direttiva #define.
Durante la compilazione la label sarà sostituita dal testo sottinteso.
#define SUPCIRC (x) (3.14*(x)*(x))
#define max(a,b) ((a) > (b) ? (a) : (b))
Nei linguaggi ad alto livello, il problema dell' occupazione di memoria e del
tempo di esecuzione assumono aspetti differenti da quelli dell' Assembly, di
cui, peraltro, tutti questi linguaggi possono includere parti nel codice
sorgente.
|