* * Veniamo ora all'utilizzo dei puntatori. * Premetto che i puntatori non sono presenti nel Java, quindi non troverete * molti esempi... e se usate questo tutorial solo come preparazione al Java, * vi consiglio di saltare l'argomento. * Un puntatore e' una variabile contenente un indirizzo di memoria. * Questo indirizzo puo' essere l'indirizzo dove si trova un'altra variabile, * un'array, una funzione o qualsiasi altra cosa. * Se una variabile contiene l'indirizzo di un'altra variabile, si dice che * la prima punta alla seconda. * Occorre a questo punto chiarire cosa e' un indirizzo di memoria. * La memoria si misura in byte, per cui un Kb sono 1024 byte e 1MB sono * un milione di byte circa. Un Kb sono 1024 byte perche' si basa sulle * potenze del 2 (2,4,8,16,32,64,128....): questi numeri si trovano * continuamente, infatti ogni bit in piu' o in meno che si ha a disposizione * nella notazione binaria (0 o 1 solamente) raddoppia le possibilita' * numeriche componibili. * Un byte a sua volta e' composto da 8 bit, per cui 2 byte sono 16 bit e * 4 byte sono 32bit. * La memoria e' organizzata come una serie consecutiva di byte, come fosse * un unico grande array monodimensionale di tipo char. * In realta' tra processori diversi e modi diversi di operare di questi ci * possono essere delle variazioni, come la segmentazione in blocchi di 64k, * ma la regola della fila di byte consecutivi e' universale. * Quando si usa il sistema operativo (DOS/WIN o altri) non ci accorgiamo di * questo, semplicemente possiamo sapere quanta memoria e' libera, e cio' * viene espresso in byte. * Avendo stabilito che la memoria e' una serie di byte consecutivi, come * si fa a distinguere un byte da un'altro? * Abbiamo fatto il paragone con un array monodimensionale: se ricordate * per accedere ai singoli byte si deve specificare un indice, ossia il * numero del byte, considerando il primo come lo 0, il secondo come l'1 * e cosi' via. * Ebbene, la memoria e' organizzata in questo modo: ogni byte ha un * indirizzo, come se la memoria fosse una strada con le case tutte uguali * e solo da un lato, ognuna delle quali e' un byte: il numero civico di * quelle case e' il loro indirizzo. * Per esempio, l'indirizzo 0 indica il primo byte della memoria, mentre * l'indirizzo 100 indica il 101esimo byte di memoria. * Quindi, l'indirizzo 100 dista dal 115 ben 16 byte! * E' ovvio che in sistemi con molta memoria, questa puo' non essere un * unico blocco da 0 ad x, ma ci possono essere vari blocchi di 1MB o piu' * non consecutivi. * Abbiamo detto che i puntatori "puntano" ad un indirizzo. * Quando compiliamo un programma in C, anche se non ce ne accorgiamo, non * facciamo che far scrivere in memoria le istruzioni assembly relative * alle funzioni che chiamiamo, assieme a blocchi di dati, ossia le * variabili e gli array. Ogni funzione e ogni variabile o array sara' * quindi posizionata ad un certo indirizzo preciso in memoria, che noi * non conosciamo, perche' tutto avviene automaticamente. * Mettiamo che la variabile pippo, di tipo intero, venga posizionata * all'indirizzo 1000: in questo caso quando scriviamo o leggiamo pippo, * fisicamente si legge e si scrive alla locazione 1000. * Quindi un puntatore che "punta" a pippo, conterra' l'indirizzo 1000, * ossia l'indirizzo di pippo. * In altri termini contiene la sua posizione in memoria. * Occorre specificare un'altra cosa: non tutti i tipi di variabile * occupano lo stesso spazio in memoria. * Per esempio, le variabili char occupano 1 solo byte, mentre gli int ne * occupano 2, e i float ben 4. * Questo significa che se abbiamo un array composto da 4 elementi di tipo * char, l'array in totale sara' lungo 4 byte. * Se abbiamo un array composto da 4 elementi di tipo int, occupera' ben * 8 byte, ossia 2*4. * Se infine l'array fosse composto da 4 elementi di tipo float, la * lunghezza complessiva sarebbe di 4*4=16 byte. * Supponendo che quest'ultimo array di tipo float si trovi a partire * dall'indirizzo 1000, il secondo elemento inizia all'indirizzo 1004, il * terzo all'indirizzo 1008, il quarto a 1012. L'array quindi va da 1000 * a 1016. * Quando operiamo con un array, pero', basta mettere il numero di elemento * come indice, e il C a seconda del tipo di dato salta 1, 2 o 4 byte per * individuare l'elemento successivo. * Quando si opera con i puntatori occorre sapere queste cose, perche' * al momento della dichiarazione di un puntatore si deve stabilire a che * tipo di dato puntera', per cui quando lo incrementeremo saltera' * 1,2 o 4 byte a seconda del tipo a cui punta. Ad esempio: * * int *conta1; * char *conta2; * * *conta1++ * punta al prossimo int (incrementa di 2 bytes) * *conta2++ * punta al prossimo char (incrementa di 1 byte) * * Inoltre, quando si fanno degli assegnamenti con un puntatore, saranno * trasferiti 1,2, 4 o piu' bytes dall'indirizzo a cui punta, a seconda * del tipo a cui punta. Se il puntatore e' stato definito come puntatore * di tipo char, e si copia in una variabile int, sara' trasferito solo * 1 byte (non 2) nella variabile int, dall'indirizzo a cui puntava il * puntatore. Se puntava un dato di tipo float avrebbe copiato troppo, 4 * bytes. Questi errori portano a malfunzionamenti seri dei programmi e * di tutto il sistema operativo con pericoli di inchiodamento. * Veniamo alla sintassi di dichiarazione di un puntatore: * * tipo *nomepunt; * * Ad esempio: * * int *puntapippo; * * La dichiarazione e' simile a quella delle variabili, ma il nome deve * essere preceduto da un asterisco. * Ricordiamo che il tipo indica il tipo di dato a cui puo' puntare. * Come usiamo un puntatore? * Esistono 2 operatori puntatore, che devono precedere le variabili e i * puntatori in questo modo: * * &pippo; * *pippo * * Vediamoli in dettaglio: * * & Indica l'INDIRIZZO (la posizione) in memoria della variabile * * * Fa accedere al CONTENUTO della variabile puntata. * Da non confondere con la moltiplicazione. * * Quindi, una volta definito un puntatore, se vogliamo immetterci * l'indirizzo di una variabile, dobbiamo usare &: * * int *puntapippo; * Definisco un puntatore di interi * int pippo; * Definisco una variabile intera * * puntapippo = &pippo; * Immetto nel puntatore puntapippo * * l'indirizzo di pippo, ossia lo faccio * * puntare a pippo. * * Allo stesso modo, ora che puntapippo punta a pippo, posso accedere * indirettamente a pippo tramite *puntapippo: * * *puntapippo = 100; * Scrivo 100 nella variabile pippo tramite * * il puntatore puntapippo. * * printf("Pippo vale %d ", pippo); * Pippo vale 100! * * * Vediamo funzionare questo programmino di esempio. */ #include <stdio.h> /* Includiamo la libreria standard */ /* Funzione principale e inizio del programma */ int main(void) /* Funzione principale, eseguita per prima */ { /* Inizio della funzione main() */ int *puntapippo; /* Definisco un puntatore di interi */ int pippo; /* Definisco una variabile intera */ pippo = 50; /* Assegno a pippo il valore 50 */ puntapippo = &pippo; /* Immetto nel puntatore puntapippo * l'indirizzo di pippo, ossia lo faccio * puntare a pippo. */ /* Allo stesso modo, ora che puntapippo punta a pippo, posso accedere * indirettamente a pippo tramite *puntapippo: */ *puntapippo = 100; /* Scrivo 100 nella variabile pippo tramite * il puntatore puntapippo. */ printf("Pippo vale %d ", pippo); /* Pippo vale 100! */ return(0); /* la funzione main restituisce uno 0 intero */ } /* Fine della funzione main() */ /* Una nota: avete presente la sintassi dello scanf, in cui la variabile * deve essere preceduta da &? * Ebbene, la funzione scanf vuole in entrata l'indirizzo della variabile * dove salvare il dato introdotto da tastiera. * Come vedete i misteri si stanno chiarendo tutti. */