Funzioni
Finora abbiamo scritto un solo “flusso” di codice, ma possiamo già individuare alcuni frammenti che sembrano ridondanti. Usiamo le funzioni per “sfoltire” il codice!
Per esempio, in tre punti stiamo copiando pezzi di memoria.
Scriviamo una funzione sotto la jp Main
e chiamiamola Memcpy
, come l’analoga funzione C:
; Copy bytes from one area to another.
; @param de: Source
; @param hl: Destination
; @param bc: Length
Memcopy:
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or a, c
jp nz, Memcopy
ret
La nuova istruzione ret' dovrebbe catturare immediatamente l'attenzione. È, senza sorpresa, quella che fa sì che l'esecuzione *ritorni* al punto in cui la funzione è stata *chiamata*. È importante notare che molti linguaggi prevedono una "fine" precisa per una funzione: in C o Rust, è la parentesi graffa di chiusura
}; in Pascal o Lua, la parola chiave
end, e così via; la funzione ritorna implicitamente quando l'esecuzione raggiunge la sua fine. Tuttavia, **non è così in assembly**, quindi bisogna ricordarsi di aggiungere un'istruzione
ret` alla fine della funzione per ritornare da essa!
Altrimenti, i risultati sono imprevedibili.
Notate il commento sopra la funzione, che spiega quali registri prende in ingresso. Questo commento è importante per sapere come interfacciarsi con la funzione; l’assembly non ha parametri formali, quindi i commenti che li spiegano sono ancora più importanti che in altri linguaggi. Ne vedremo altri man mano che procediamo.
Ci sono tre punti nel codice di inizializzazione in cui possiamo usare la funzione Memcpy
.
Trovate ognuno di questi cicli di copia e sostituiteli con una chiamata a Memcpy
; per questo, utilizziamo l’istruzione call
.
I registri servono come parametri alla funzione, quindi li lasceremo così come sono.
Prima di | Dopo |
---|---|
|
|
|
|
|
|
Nel prossimo capitolo, scriveremo un’altra funzione, questa volta per leggere gli input del giocatore.