Tutorials - PIC

 

Macro vs. Subroutine


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:

  • Una SUBROUTINE può essere scritta in qualsiasi punto del programma e non è necessario che si trovi prima di una sua chiamata. Anzi, potrà trovarsi benissimo in fondo alla memoria programma; basta che la chiamata tenga conto di come deve essere maneggiato il Program Counter per il salto.

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:

  call Delaysub  

ovvero 2 byte e il tempo necessario alla sua esecuzione (2 cicli).

Ogni volta che nel sorgente sarà specificato:

  Delaymacro  

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:

  1. 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.
      
  2. 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.


 

 

Copyright © afg. Tutti i diritti riservati.
Aggiornato il 03/11/11.