ELEMANIA
Z80 - Funzioni ricorsive
Funzioni ricorsive

Vogliamo adesso fare un esempio dei problemi che si incontrano quando si vuole gestire il passaggio di parametri e il valore di ritorno di una subroutine in assembly.

A tale scopo consideriamo la seguente funzione in linguaggio C per il calcolo della somma di tutti gli interi fino a un valore num dato. Per esempio con num=4 la funzione calcola la somma di 1+2+3+4=10

int sum(int num)
{
if (num!=0)
    return num + sum(num-1); // la funzione chiama se stessa
else
    return num;
}

Questa semplice funzione fa uso di una delle tecniche di programmazione più avanzate e anche più difficili da comprendere per i principianti: la ricorsività.

In informatica viene detto ricorsivo un algoritmo espresso in termini di se stesso, ovvero in cui l'esecuzione dell'algoritmo comporta l'esecuzione di numerose copie (o istanze) dell'algoritmo stesso.

Si consideri l'esempio della funzione precedente. Se il valore di num è diverso da zero, la funzione chiama se stessa. Tuttavia il valore passato a questa seconda istanza della funzione non è il valore originale di num, ma il valore decrementato di una unità (num-1).

In questo modo prima o poi (dopo un certo numero di chiamate ricorsive) il valore di num diventa zero e dunque la ripetizione dell'algoritmo non diventa infinita.

Questa esecuzione ricorsiva è possibile in C in quanto ogni istanza della funzione num ha il proprio spazio di variabili locali che non condivide con nessuno (in questo caso lo spazio consiste nella variabile num). Infatti, sebbene tutte queste variabili num abbiano lo stesso nome, non si tratta affatto della stessa variabile. Pertanto ogni variabile num locale a un'istanza della funzione può assumere valori diversi rispetto alle altre.

Questa creazione di uno spazio privato per le variabili delle funzioni può essere realizzata solo facendo uso dello stack, come si vedrà provando a scrivere il codice assembly per la nostra funzione.

Una subroutine ricorsiva in assembly

La codifica in assembly di algoritmi ricorsivi presenta la necessità di implementare il salvataggio e il ripristino dallo stack dello spazio delle variabili locali a ogni istanza della subroutine.

Si consideri l'esempio seguente, in cui la funzione ricorsiva sum precedente è tradotta in assembly Z80:

main:
	LD SP,0  ; inizializza lo stack pointer
        IN A,(0)  ; legge il valore iniziale di num
	PUSH AF   ; salva il valore sullo stack
	CALL sum  ; chiama la funzione ricorsiva
	POP AF    ; recupera dallo stack il valore del risultato
        OUT (0),A ; visualizza il risultato
	HALT
sum:
	POP DE	; estrae l'indirizzo di ritorno dallo stack
	POP AF	; estrae dallo stack il parametro (valore di num)
	PUSH DE	; ripristina l'indirizzo di ritorno sullo stack
	PUSH BC	; salva lo spazio delle variabili locali del chiamante
	CP 0       ; se A è zero (cioè se num è zero)
	JP Z,fine
	LD B,A	   ; copia in B il valore di A (num)
	DEC A	   ; decrementa il valore
	PUSH AF	   ; salva sullo stack
	CALL sum   ; chiamata ricorsiva
	POP AF	   ; recupera il risultato
	ADD A,B	   ; somma num col risultato della chiamata
fine:
	POP BC	; ripristina le variabili del chiamante
	POP DE	; estrae l'indirizzo di ritorno
	PUSH AF ; mette sullo stack il risultato
	PUSH DE	; ripristina l'indirizzo di ritorno
	RET

Si osservi che, al momento della chiamata della subroutine ricorsiva, il valore su cui bisogna fare il calcolo (num, contenuto in A) viene salvato sullo stack. In questo modo però tale valore si trova nello stack al di sotto del valore dell'indirizzo di ritorno della chiamata (salvato in automatico al momento della CALL).

Per questa ragione all'inizio della subroutine occorre prima estrarre il valore di tale indirizzo dallo stack (per poter recuperare il valore sottostante di num) e quindi rimetterlo sullo stack stesso:

sum:
	POP DE	; estrae l'indirizzo di ritorno dallo stack
	POP AF	; estrae dallo stack il parametro (valore di num)
	PUSH DE	; ripristina l'indirizzo di ritorno sullo stack
	PUSH BC	; salva lo spazio delle variabili locali del chiamante

L'ultima istruzione PUSH BC serve invece per salvare sullo stack lo spazio delle variabili (registri) usati dal chiamante. Infatti tutte le istanze della subroutine usano lo stesso registro B per effettuare la somma, ma ogni copia del registro deve poter conservare un differente valore.

 

precedente - successiva

Sito realizzato in base al template offerto da

http://www.graphixmania.it