Newsgroup: it.comp.lang.c
Titolo: Puntatori

Ho appena iniziato a progammare in C:
Qualcuno potrebbe spiegarmi con un esempio mOOOOOOOOOOOOlto performante(!)
come si usano a cosa servono perchè ecc ecc.
Grazie
James

come si usano a cosa servono perchè ecc ecc.
In C non puoi fare a meno dei puntatori:
-- tutte le volte che una funzione deve modificare un
   parametro che le viene passato;
-- tutte le volte che vuoi decidere durante l'esecuzione
   del tuo programma di quanta memoria hai bisogno
   per un certo array;
-- tutte le volte che vuoi mettere insieme strutture dati
   piu` ricche o complesse di un semplice array;
-- in varie altre situazioni piu` "specialistiche".
Non e` questione di "molto performante" ovvero poco,
e` che, in C, senza puntatori, praticamente non si
comincia neppure a fare programmi che non siano
meri giocattoli.  (Questa e` una delle cose che rende
il C++ piu` facile da imparare del C, nonostante che
ne sia praticamente un sovrainsieme: in C++, grazie
alle reference, alle ricche strutture-dati predefinite
dalle librerie standard, ecc ecc, si puo` andare MOLTO
avanti prima di dovere introdurre allo studente i vari
usi dei puntatori...!).
Comunque, tornando al C, come vedi, un esempio
non basta, perche` gli usi dei puntatori sono tanti e
molto diversi fra di loro.  Ecco comunque un esempio
del caso piu` semplice:
void
scambia_interi(int* primo, int* secondo)
{
    int temporaneo = *primo;
    *primo = *secondo;
    *secondo = temporaneo;
}
Questa funzione ha lo scopo di scambiare fra di
loro i propri due argomenti, e quindi, dovendo appunto
modificare gli argomenti stessi, *deve* avere come
argomenti due puntatori.  Ecco come usarla:
int unavariabile = 23;
int unaltra = 45;
printf("Prima: una=%d, l'altra=%d\n",
               unavariabile, unaltra);
scambia_interi(&unavariabile, &unaltra);
printf("Dopo: una=%d, l'altra=%d\n",
               unavariabile, unaltra);
Come vedi, per ottenere i due puntatori da
passare alla scambia_interi(), usiamo "&",
l'operatore "indirizzo-di"; applicato ad una
variabile, ad esempio di tipo int, esso torna
un puntatore, qui di tipo int*, tale che quando
"de-referenziamo" quel puntatore (cioe` gli
applichiamo l'operatore "*") stiamo trattando
il contenuto di quella variabile (e possiamo
sia "leggerlo", sia, anche, "scriverlo", cioe`
modificare la variabile stessa con un assegnamento
al puntatore de-referenziato).
(In Pascal, lo stesso effetto si avrebbe, senza
puntatori, dichiarando un argomento "var"; in
C++, dichiarandolo "reference"; altri linguaggi
passano sempre o per default gli argomenti alle
funzioni/procedure con questo schema; ma in C,
gli argomenti sono invece sempre passati per
valore, quindi dobbiamo esplicitamente usare i
puntatori in questo caso).
Quando riesci a digerire questo primo uso
fondamentale magari passiamo ad altri piu`
"sofisticati"...
Alex

come si usano a cosa servono perchè ecc ecc.
Cominciamo col dire una cosa che forse sai gia' e cioe' che un puntatore e' una
variabile di grandezza definita contenente un indirizzo di memoria.
Si dice infatti che esso _punta_ ad una locazione di memoria.
Ovviamente la dimensione della variabile puntatore dipende dal modo in cui e'
gestita la memoria del sistema.
Sotto DOS la dimensione e' di 4 byte.
Esistono due operatori in C che sono & (ampersand) e * (asterisco) i quali si
applicano rispettivamente a tutti i tipi di variabili e alle variabili puntatore
( o indirizzi in generale ).
Si usano infatti come se fossero due funzioni che ritornano, rispettivamente,
__l'indirizzo__ di una variabile e  il __valore puntato__ dalla variabile
puntatore ( o comunque presente a un tale indirizzo ).
Considerando le dichiarazioni d'esempio :
"int a" e "int *b"
Nel primo caso si dichiara la variabile "a" come una variabile intera.
Nel secondo caso si dichiara la variabile "b" come una variabile puntatore.
L'indirizzo di a sara' costituito dalla quantita' "&a", mentre il valore puntato
da b sara' costituito da "*b"
Ovviamente valgono le seguenti equivalenze :
a = *(&a)    (il valore di a e' uguale al valore puntato dall'indirizzo di a)
b = &(*b)   (il valore di b e' l'indirizzo dell'intero da b puntato)
I possibili usi di queste due variabili potrebbero essere :
a = 3 ;
b = &a ;  (assegna a b l'indirizzo di a)
E' assolutamente intuitivo come, a questo punto, scrivere *b = 5 equivalga a
scrivere a = 5.
Si compie, cioe', una modifica su a, passando attraverso b. 
Ora un concetto fondamentale : I parametri delle funzioni in C sono passati per
default come copia del loro valore nello spazio locale alla funzione stessa,
nello stack, ed ogni operazione su essi e' compiuta localmente.
Ecco perche' se si volesse costruire una funzione "somma", alla quale passare
tre parametri, dei quali i primi due fossero gli operandi e il terzo la somma di
essi, sarebbe sbagliato scrivere la funzione come :
void somma(int a, int b, int c)
{
  c = a+b;
}
Perche' c verrebbe modificato localmente e all'uscita dalla funzione
riacquisterebbe il valore originario.
L'unico modo, invece, per costruire un _riferimento_ alla variabile esterna alla
funzione e' quello di usare un puntatore.
La versione corretta della funzione e' 
void somma(int a, int b, int *c)
{
  *c = a+b;
}
In questo modo alla funzione non viene passato in c un valore numerico, ma un
indirizzo (grazie all'*) e la somma viene "salvata" a partire dalla locazione
_puntata_ dal puntatore "c".
I puntatori in generale vengono usati per costruire dei tipi di dati molto
complessi e hanno praticamente applicazioni infinite.
Le applicazioni piu' utili sono quelle che sfruttano "l'allocazione dinamica"
della memoria.
I programmi quando vengono caricati in memoria, in fase di run-time, possono
disporre a piacimento della quantita' di memoria rimasta libera.
L'allocazione dinamica consiste nel "rubare" al sistema un po' di memoria
libera, gestirla a proprio piacimento e rilasciarla in un secondo momento.
Queste operazioni vengono compiute in fase di esecuzione mediante funzioni come
"malloc()" e "free()".
La prima si usa per allocare una certa quantita' di memoria, e ritorna
l'indirizzo della prima locazione dell'area "catturata".
Da questo punto in poi questa zona di memoria non viene piu' considerata libera.
La seconda funzione si usa per rilasciare la memoria allocata.
La memoria, appena rilasciata, riappare al programma come libera.
Ad esempio :
char *c;    (definisco c come puntatore a caratteri)
c = malloc(45);   (alloco 45 byte di memoria)
if ( !c )               (se l'operazione e' fallita) 
   puts("operazione fallita");
else
{
   puts("operazione riuscita");
   free(c);   (libera la memoria precedentemente allocata)
}
Ovviamente questo e' un esempio che non sfrutta la memoria allocata, ma si
limita a comunicare al programmatore che e' riuscito (o non e' riuscito) a
trovare una zona di 45 byte libera.
Se vuoi approfondire il modo in cui si utilizzano i puntatori per applicazioni
piu' complesse puoi scrivermi in privato.
Ciao !
Andrea Laforgia
(remove NOSPAM from my address before reply via e-mail)
ICQ #8328956

Sotto DOS la dimensione e' di 4 byte.
e' un'affermazione imprecisa, il dos non ha nulla a che fare con la
dimensione dei puntatori, se usi un compilatore che genera codice a
16bit puoi avere puntori *near* di 16bit e *far* di 32bit. Se usi un
dos extender e un compilatore a 32bit puoi avere puntatori *near* di
32bit e *far* di 48bit.
Gianpaolo Bottin
- The truth is out there -


Anche questa non e' certo una verita' assoluta, dipende dal compilatore che
usi, cosi' come il concetto di far e near non ha niente a che vedere con il
c.
-- 
Warning!
Don't send any mail for me at my from: or replayto: addresses ;
I just kill the mail over there without reading!

usi, cosi' come il concetto di far e near non ha niente a che vedere con il
in che senso non e' una verita' assoluta ? Che io sappia tutti i
compilatori C che hanno il concetto di far/near per i puntatori hanno
le dimensioni che ho elencato, e non per caso ma perche' e' cosi' che
funzionano le cpu Intel.
Certo che near e far non hanno strettamente a che fare con il C pero'
tutti i compilatori C/C++ per ms-dos hanno il concetto di near/far, e
anche nel piu' banale dei programmi non puoi trascurare una cosa cosi'
fondamentale, 64k finiscono molto in fretta. Solo in ambienti con
indirizzamento flat a 32bit ci si puo' permettere di trascurare il
reale modo in cui funzionano le cpu Intel, ma se anche lavori con un
dos-extender a 32bit per certe cose dovrai comunque fare i conti con
l'architettura segmentata delle cpu x86.
Gianpaolo Bottin
- Live Long and Prosper -

l'architettura segmentata delle cpu x86.
Ripeto, con il djgpp non e' cosi'. Lavori appunto con il modello flat a 32
bit, il dos extender si prende cura di dove e come allocare la memoria, e
puoi benissimo lavorare ignorando la cervellotica architettura delle cpu
x86.


x86.
so benissimo come funzionano i dos-extender ho utilizzato il Dos4gw,
il Phar Lap ed un altro paio con il Watcom C, e' vero che ti
nascondono abbastanza bene l'architettura segmentata ma in alcune
occasioni salta nuovamente fuori, ad esempio quando devi accedere a
schede memory mapped oppure per richiamare delle parti di codice in
real mode. In queste occasioni e' comodo non solo poter utilizzare da
C i puntatori far ma anche avere a disposizione funzioni come la
_fmemcpy() che accettano puntatori far.


x86.
Ciņ lo reputerei, per quanto la mia opinione possa contare,+
uno svantaggio che un vantaggio. Per quanto l'extender possa nascondere la
"cervellotica" architettura del processore....beh alla fine questa viene
sempre fuori !!!
saluti
William

real mode. 
Francamente non ho mai usato niente del genere, ma credo che tutto cio' sia
possibile, come dicevo sopra, smanettando con le varie funzioni collegate
all'extender. 
Comunque, lungi da me voler scatenare guerre sante su quale compilatore sia
meglio, il djgpp e' tutto sommato un bel prodotto. E' il porting dos del
gcc, il compilatore c dello gnu project, e ne esistono porting per quasi
tutte le piattaforme e sistemi operativi esistenti. E poi e' gratis (scusa
se e' poco! ;-)) )


William
Ciao.


se e' poco! ;-)) )
con quel che costano i compilatori, non e' poco, anzi e' un bel
vantaggio ;)
