Le strutture di controllo ASM
Introduciamo adesso alcune strutture fondamentali della programmazione utili in tantissime situazioni. In ogni programma, degno di tale nome, capita spesso di dover ripetere le stesse operazioni (un lavoro elementare) per un certo numero di volte. Un ciclo (loop) è una struttura di controllo di un programma che esegue per molte volte lo stesso insieme di istruzioni. Si dice anche che il ciclo è una struttura iterativa.
In un ciclo si possono distinguere quattro fasi: inizializzazione, lavoro, aggiornamento e controllo.
Esistono due possibilità di base per impostare correttamente un ciclo, entrambe fanno riferimento alla posizione del modulo di controllo rispetto agli altri moduli. Le figure indicano le due situazioni tipiche: con controllo in testa (prima di cominciare il blocco di lavoro), o con controllo in coda (dopo aver eseguito il lavoro per la prima volta).
Nei diagrammi delle figure si vede facilmente come il programma giri dentro al ciclo (parte rossa) fino a che la condizione di controllo non lo fa uscire (parte verde). Un giro di un ciclo è ciò che si intende con il termine iterazione.
I puristi indicano molti motivi per utilizzare cicli con controllo in testa (sicurezza, integrità , etc.), ma programmando in Assembly x86 questa indicazione viene spesso disattesa, perché il programma risulta di lettura più semplice se il controllo è in coda ed anche perché l'istruzione specifica per realizzare i cicli (LOOP) usa un controllo in coda. In ogni caso sarà poi l'esperienza a guidare il programmatore nella scelta migliore a seconda delle situazioni.
Di norma si conosce a priori il numero di iterazioni, ma non è sempre così. Come primo esempio supponiamo di voler eseguire 7 volte un certo lavoro e troviamo delle soluzioni intuitive che sfruttano i 2 diagrammi visti in precedenza. In questo tipo di cicli nell'inizializzazione si deve impostare un contatore, CX, che terrà traccia in ogni istante del numero di giri che sono stati compiuti. Chiameremo questo contatore "contatore di controllo". La fase di aggiornamento modifica il contatore per segnalare che è stata eseguita un'altra iterazione. Il test stabilisce se il ciclo è stato percorso il giusto numero di volte e se è giunto il momento di uscirne.
Vediamo ora la realizzazione in Assembly dei due cicli illustrati dai diagrammi di flusso.
|
|
Dopo aver esaminato i due casi classici, vediamo come sia possibile arrivare ad una soluzione ottimizzata con l'uso di vari accorgimenti. Il modo più efficiente per fare i cicli in Assembly utilizza un contatore a decremento ed un controllo in coda.
|
Nella prima versione, come si vede, manca la cmp finale che non è più necessaria perché il ciclo, andando a rovescio, finisce sempre con lo zero e la condizione di zero viene rilevata direttamente dalla dec. Con questa versione si risparmia una cmp per ogni iterazione del ciclo. Il risparmio di un'istruzione può sembrare un'inezia, ma diventa significativo se fatto in cicli che si ripetono per centinaia/migliaia di volte, come accade per esempio in moduli che fanno parte del sistema operativo o di applicazioni grafiche.
Nella seconda versione è stata utilizzata l'istruzione "loop iteraz" che semplifica ulteriormente la scrittura del ciclo in quanto raggruppa la "dec cx" e la "jnz iteraz". E' importante notare che l'istruzione LOOP presuppone l'uso esclusivo del registro CX con funzione di contatore. Anche se non è esplicito nella scrittura dell'istruzione il registro CX viene sempre decrementato dalla LOOP. Ogni volta che stiamo per usare una LOOP dobbiamo chiederci cosa c'è attualmente in CX e se quello che c'è è il valore giusto per il contatore del ciclo, altrimenti i risultati possono essere imprevedibili. L'uso della loop rende certamente più compatto il codice anche se con l'evoluzione delle CPU x86, dal 486 in avanti, il risultato non è più veloce delle altre istruzioni equivalenti.
Chi pensa molto, non è adatto a esser uomo di partito: troppo presto penserà se stesso attraverso il partito.
Friedrich W. Nietzsche