L'ingresso e uscita di dati nel linguaggio assembly riguardano esclusivamente stringhe; in caso si debbano inserire o visualizzare valori numerici occorre operare le necessarie conversioni da programma (più avanti verranno mostrati esempi a tale proposito).
Per gestire l'input e l'output si deve ricorrere a delle funzioni del sistema operativo, dette «chiamate di sistema» o syscall.
Per la lettura e la scrittura si usano rispettivamente le chiamate di sistema corrispondenti alle funzioni read e write del linguaggio c (si ricordi che il sistema operativo GNU/Linux è scritto in c, quindi le chiamate di sistema hanno una stretta corrispondenza con le funzioni a basso livello di tale linguaggio).
Per avere maggiori dettagli circa queste due funzioni si può usare il manuale in linea di GNU/Linux, ad esempio con il comando:
$
man read
Un esempio di chiamata di sistema molto usata (forse la più usata) è quella per l'uscita dal programma, attivata ponendo il valore 1 nel registro eax prima del richiamo dell'interruzione software 8016 (istruzione: int $0x80).
Per attivare la chiamata di sistema per la lettura occorre ovviamente richiamare la stessa interruzione software, valorizzando però i registri in modo più articolato:
eax: deve contenere il valore 3 corrispondente appunto alla funzione di lettura;
ebx: deve contenere il numero identificativo del file da cui leggere; nel nostro caso si tratta del file speciale stdin corrispondente alla tastiera e associato al valore 0;
ecx: deve contenere l'indirizzo di partenza della stringa che accoglierà l'input;
edx: deve contenere il numero di caratteri da leggere.
Dopo l'esecuzione della chiamata, nel registro eax, viene ritornata la lunghezza effettiva della stringa letta, comprensiva del carattere «invio» ('\n') con cui si conclude la sua immissione.
Per la scrittura a video la situazione è simile e si devono usare:
eax: deve contenere il valore 4 corrispondente alla funzione di scrittura;
ebx: deve contenere il numero identificativo del file su cui scrivere; nel nostro caso si tratta del file speciale stdout corrispondente al video e associato al valore 1;
ecx: deve contenere l'indirizzo di partenza della stringa da visualizzare;
edx: deve contenere il numero di caratteri da scrivere.
Dopo l'esecuzione della chiamata, nel registro eax, viene ritornata la lunghezza effettiva della stringa visualizzata.
Come primo esempio vediamo un programma che stampa a video il messaggio «Ciao a tutti».(1)
|
Essendo questo un programma che prevede una qualche interazione durante la sua esecuzione, nella figura C.2 possiamo vedere, dopo i comandi per la sua traduzione e linking, l'effetto della sua esecuzione con la stampa a video della stringa.
|
Nel prossimo esempio vogliamo invece stampare a video una stringa immessa da tastiera.(2)
|
Notiamo l'utilizzo del segmento bss, nel quale viene dichiarata l'etichetta da usare per l'output.
Nella figura C.4 vediamo il risultato dell'esecuzione del programma.
|
Come detto, in assembly l'input e l'output riguardano solo stringhe: se inseriamo un valore numerico questo viene acquisito come una stringa composta dai simboli corrispondenti alle cifre da cui è costituito; viceversa se vogliamo visualizzare un valore dobbiamo trasformarlo nella corrispondente stringa.
Nel prossimo esempio vengono mostrati entrambi questi procedimenti in un programma che accetta da tastiera due valori interi, ne calcola il prodotto ed emette a video il risultato.
In questo caso ripristiniamo la numerazione delle righe del listato perché i procedimenti di conversione non sono banali e meritano qualche spiegazione più accurata.(3)
|
Nelle righe da 21 a 25 il programma visualizza un messaggio con la richiesta di inserimento di un valore; tale inserimento avviene grazie alle righe da 27 a 31, mentre alla riga 32 si salva la lunghezza effettiva della stringa immessa (compreso l'invio).
Il numero immesso in stringa è, appunto, una stringa e deve essere convertito nel corrispondente valore; il procedimento usato può essere così riassunto:
si parte dalla cifra più a sinistra e si somma ogni cifra a un totalizzatore;
prima di passare alla cifra successiva si moltiplica il totalizzatore per 10;
arrivati all'ultima cifra a destra, nel totalizzatore abbiamo il valore corrispondente alla stringa di cifre di partenza.
Le righe da 34 a 46 realizzano quanto appena descritto; in dettaglio:
alla riga 34 si azzera il registro indice esi;
alle righe 35 e 36 si azzera il totalizzatore ax e si pone il valore 10 in bx per le successive moltiplicazioni;
alle righe 37 e 38 si pone in cx la quantità si caratteri della stringa, meno uno per non considerare anche il carattere invio;
a riga 39 inizia il ciclo da ripetere tante volte quante sono le cifre della stringa, grazie al controllo di riga 46;
alla riga 40 si moltiplica il totalizzatore per 10; la prima volta sarebbe inutile in quanto ax in quel momento vale zero;
alla riga 41 si sposta una cifra della stringa (quella indicizzata da esi) in dl; in questa istruzione si gestisce la stringa come un vettore come mostrato nel paragrafo 3.18;
alla riga 42 si sottrae 3016 da dl per ottenere il valore corrispondente a quella cifra;
alle righe 43 e 44 si somma tale valore nel totalizzatore, tenendo conto di eventuale riporto;
infine il ciclo si chiude con l'incremento dell'indice a riga 45.
A questo punto in ax abbiamo il valore da spostare in val1 (riga 47).
Dalla riga 48 alla riga 76 tutto il procedimento (messaggio, input, conversione) viene ripetuto per il secondo valore con l'ovvia differenza che stavolta esso viene spostato in val2.
Le righe 78 e 79 svolgono la moltiplicazione tra i due valori; il risultato, che è in ax, deve essere convertito in stringa.
Per fare ciò si usa il seguente algoritmo:
si divide ciclicamente il valore per 10;
ad ogni divisione, il resto rappresenta una cifra da porre nella stringa (partendo da destra);
il ciclo si arresta quando il quoziente della divisione è zero.
Le righe da 81 a 90 eseguono quanto appena illustrato; in dettaglio:
alla riga 81 si pone il valore 10 in bx per le successive divisioni;
alla riga 82 si imposta l'indice per il riempimento della stringa di output sulla posizione prevista più a destra; nel nostro caso è la quinta posizione (quella di indice quattro);
il ciclo di conversione inizia alla riga 83 ed è con controllo in coda;
alle righe 84 e 85 si divide dx:ax per 10; siccome il nostro valore da dividere è solo in ax, dx lo azzeriamo;
la divisione pone il quoziente in ax e il resto in dx (in realtà in dl visto che è minore di dieci); alla riga 86 sommiamo 3016 per avere in dl il codice ASCII della cifra corrispondente al valore del resto;
alla riga 87 tale cifra viene inserita nel vettore strout nella posizione indicizzata da esi;
alla riga 88 si decrementa l'indice, mentre alla riga 89 si verifica se ax vale zero; se tale condizione è falsa, grazie alla riga 90 si passa alla prossima iterazione.
Conclusa la conversione del valore da visualizzare, il programma emette un opportuno messaggio (righe da 92 a 96) e poi la stringa contenente il risultato (righe da 98 a 102).
Nella figura C.6 vediamo gli effetti dell'esecuzione del programma.
|
1) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/programmi-assembly/input-output1.s>.
2) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/programmi-assembly/input-output2.s>.
3) una copia di questo file, dovrebbe essere disponibile anche qui: <allegati/programmi-assembly/input-output3.s>.