Informatica – Puntatori in C

Premi qui per scaricare il PDF

[adinserter block="7"]

L’accesso standard alle variabili avviene tramite l’identificatore:

int a,b;

Prendi il valore contenuto nella cella identificata da b e memorizzalo nella cella identificata da a:

a = b; 
  • Dimensione in memoria. (gli interi vengono rappresentati su 2 byte, i caratteri su 1 byte, i reali su 4 byte).

Sizeof(variabile) → quante celle (ogni cella è un byte) vengono occupate da una variabile

Variabile

Si dicono automatiche, il calcolatore si occupa di dargli una posizione in memoria.

Ogni variabile è caratterizzata da:

  • Ingombro → grandezza della locazione di memoria (espressa in numero di celle/byte). E’ immutabile, un int rimane int.
  • Indirizzo → è l’indirizzo della locazione di memoria associata alla variabile, cioè il numero d’ordine della posizione della prima cella tra quelle associate alla variabile. E’ immutabile, perchè indica una posizione in un elenco di celle di RAM;
  • Valore → è il valore contenuto nella locazione di memoria associata alla variabile. Il valore muta durante l’esecuzione del programma.

Quando una variabile è a sinistra di un assegnamento, la macchina usa il suo indirizzo (cioè la posizione della prima cella dell’ingombro di RAM riservato per la variabile) per andarne a modificare il valore.

Quando è a destra, la macchina usa il valore della variabile.

Gli identificatori servono al programmatore per distinguere le variabili, ma per accedere alla RAM, l’esecutore usa gli indirizzi.

In C, per conoscere l’indirizzo delle locazioni di memoria associate alle variabili, si utilizza l’operatore &. Per inserire un puntatore nella printf, si utilizza il comando %p (formato pointer, equivale a %d, %c, %s).

int main()
{
	int x = 3;
	printf("Indirizzo di x: %p \\n", &x); //%p indica il puntatore
	printf("Il valore di x: %d \\n",x);
	
}

variabili dinamiche → Possono esistere anche delle variabili senza nome (hanno solo un’occupazione in memoria), alle quali si accede tramite un puntatore.

[adinserter block="7"]

Variabili puntatori

Sono delle variabili automatiche (il calcolatore assegna la posizione in memoria al momento dell’esecuzione). Le variabili puntatore sono variabili il cui valore è l’indirizzo di un’altra variabile (la posizione di memoria).

int cont = 1275;
int * punt; //Sintassi per dichiarazione puntatori
punt = &cont

La variabile puntatore, prende l’indirizzo in memoria della variabile cont e lo assegna alla variabile puntatore punt. (I puntatori possono puntare a int, char, float, struct ecc…).

typedef int* intPointer; //Definisco un tipo puntatore
intPointer myPtr;

int* Pointer; //Abbreviazione

Puntatori a dati di tipo diverso sono variabili di tipo diverso.

Deferenziazione

*myPtr;

Permette di estrarre il valore della variabile puntata dal puntatore che è argomento dell’operatore. Restituisce il valore e non l’indirizzo della variabile puntata.

int x = 3;
int*p = &x //Inizializzo p

printf("Il valore di x è %d\\n", *p); //restituisce il valore di x, non l'indirizzo

Esempio

typedef int* punt;
punt p;
int i = 5, j = 9;
p = &j; //Assegno a p l'indirizzo di j
*p = i; //Cambio il valore della variabile j con quello di i
++i; //incremento i
i = *p; //Assegno alla variabile i, il valore di *p che è 5 
(*p) ++; //Equivale a j=j+1. *p è ora 6
p = &i; //Assegno a p l'indirizzo di i
*p = j; //Assegno a *p, che corrispone a i il valore di j, che in questo caso è 6

Riassunto

La dichiarazione di una variabile p di tipo puntatore a un intero

int *p;

Con &i si denota l’indirizzo della variabile i. & restituisce l’indirizzo di una variabile. (& è chiamato operatore di referenziazione)

p = &i;

L’operatore opposto è *, che restituisce il valore puntato. ( è chiamato operatore di dereferenziazione)

i = *p;

Esempio 2

int *p, *q, x, y;
p = &x;
q = &y;

//ORA p punta a Y
p = q; //Si impone che il puntatore p punti alla stessa variabile a cui punta q

//Equivale a x = y
*p = *q; //Il valore della variabile a cui punta p, prende il valore della variabile a cui punta q
[adinserter block="7"]

NULL

  • Il valore iniziale di un puntatore dovrebbe essere la costante speciale NULL;
  • NULL significa che non ci si riferisce ad alcuna cella di memoria;
  • Deferenziando NULL si ha un errore di esecuzione.

NULL → costante simbolica che rappresenta un valore speciale che può essere assegnato a un puntatore, NULL rappresenta il valore 0.

Test di nullità di un puntatore

if (p == NULL) 

//OPPURE

if (!p)

Test di NON nullità

if (p != NULL)

//OPPURE

if (p)

Esercizio 1

Assegnare a due puntatori l’indirizzo degli elementi con valore minimo e massimo in un array

#include 
#define LUNGHEZZA 100

int main()
{
	int i, ArrayDiInt[LUNGHEZZA];
	int *PuntaAMinore = NULL, *PuntaAMaggiore = NULL;

	//Acquisizione array di interi
	
	//CERCA IL VALORE MINIMO DELL'ARRAY
	PuntaAMinore = &ArraydiInt[0];
	for (i=1; i<LUNGHEZZA; i++)
		if(ArrayDIInt[i] < *PuntaAMinore)
			PuntaAMinore = &ArraydiInt[i];
	
	//CERCA IL VALORE MASSIMO DELL'ARRAY
	PuntaAMaggiore = &ArraydiInt[0];
	for (i=1; i<LUNGHEZZA; i++) if(ArrayDIInt[i] > *PuntaAMaggiore)
			PuntaAMaggiore = &ArraydiInt[i];

	return 0;
}

Esercizio 2

Date le seguenti dichiarazioni di variabile, descrivere cosa comporta nella memoria del calcolatore l’esecuzione delle istruzioni riportate di seguito

int *pPtr = NULL, *qPtr = NULL, *rPtr = NULL;
int x = 1, y = 5;

pPtr = &x; qPtr = &x; rPtr = &y; //Assegna gli indirizzi ai puntatori
*pPtr = *pPtr * 3 + 1; //Equivale a x = x*3+1 -> 4
qPtr = rPtr; //qPtr ora punta a y, come rPtr
printf(" %d, %d, %d ", *pPtr, *qPtr, *rPtr); //Stampa 4, 5, 5
*rPtr = *qPtr; //Equivale a y = y
rPtr = pPtr; //rPtr ora punta a x 
pPtr = qPtr; //pPtr ora punta a qPtr che è uguale a y
printf(" %d, %d, %d ", *pPtr, *qPtr, *rPtr); //Stampa 5, 5, 4

Puntatori e tipo delle variabili puntate

Il compilatore segnala l’uso dei puntatori a dati di tipo diverso da quello a cui dovrebbero puntare:

  • In forma di warning: sono errori potenziali.

I tipi “puntatore a tipo x” e “puntatore a tipo y” sono diversi tra loro

  • Il tipo void*, però, è compatibile con i puntatori a tutti i tipi:

    • il suo significato è quello di indicare valori di tipo indirizzo di memoria senza alcuna ulteriore specificazione. Punta a qualcosa di cui non conosco il tipo
    • (Dobbiamo fare un casting per utilizzare il puntatore void).
    int *pPtr;
    void *superPtr; 
    
    superPtr = pPtr; //NON SI PUO' FARE
    
    (int *)superPtr = pPtr; //Casting di variabile puntatore a int *
    
[adinserter block="7"]
  • Esempio puntatore void

    #include 
    int main ()
    {
    	int x = 3, y = 0, *myPtr;
    	void *superPtr;
    	
    	myPtr = &x; //Assegno a una variabile punt a int, l'indirizzo di un int
    	printf("\\n Indirizzo di x: %p", myPtr);
    	printf("\\n Valore di x: %d", x);
    
    	superPtr = (void*)&y; //spiegazione sotto
    
    	superPtr = (void*)myPtr; //copia di puntatori con promozione di int* a void*
    	printf("\\n Valore di superPtr: %p\\n", superPtr);
    
    	y = *((int) superPtr); //Spiegazione sotto
    
    	printf("\\n Valore di y: %d \\n", y);
    	return 0;
    }
    

    superPtr = (void)&y;* → una variabile di tipo void* puo’ contenere l’indirizzo di una variabile di tipo qualunque. (E’ buona abitudine evidenziare sempre nel codice le promozioni (come in questo caso) o le coercizioni di valori di tipo diverso) → evidenziare il casting. Se non lo mettiamo, il casting è eseguito automaticamente.

    y = ((int) superPtr); Assegno a una variabile il valore della cella di memoria con indirizzo indicato da superPtr, mettendo in evidenza che il contenuto della variabile superPtr deve essere interpretato come un valore di tipo (int ). E’ una coercizione di tipo da void* a int*

I puntatori const

Le variabili puntatore possono essere dichiarate const

  • Possono essere const (NON sovrascrivibili) sia i valori referenziati, sia il valore dell’indirizzo contenuto nella variabile.
int k = 0, a[] = {1,2,3}; //Valori modificabili
const int = 0; //Variabile di valore costante 0

int* const m = &k; //Puntatore costante a int - valore di m è costante
const int* n = &i; //puntatore a un valore costante di tipo int - Il contenuto della cella di memoria referenziata da n (quella di i) è costante 
int* const p = a; //puntatore costante a int (array, cella a[0])
const int* const q = b; //Puntatore costante a un valore costante di tipo int

Gli statement che indicano una dichiarazione di variabili, hanno significato leggendoli da destra verso sinistra.

  1. int const m = &k;* → Puntatore costante a int – valore di m è costante. NON SI PUO’ FARE m++
  2. const int n = &i;* → puntatore a un valore costante di tipo int – Il contenuto della cella di memoria referenziata da n (quella di i) è costante. NON SI PUO’ FARE →**n = 5, n++. SI PUO’ FARE → n++
  3. int const p = a;* → puntatore costante a int (punta ad un array, cella a[0]). Equivale a int * const p = &a[0]. Il valore di a non può essere cambiato.
[adinserter block="7"]

Array e Puntatori

In C esiste una parentela tra array e puntatori. Il nome di un array è una costante (simbolica) di tipo puntatore, di valore “indirizzo della prima cella allocata per l’array”.

int v[3];

definisce v come una costante simbolica il cui tipo è int* const (punto 1 del paragrafo sopra), cioè un puntatore costante a un intero.

Perciò v[i] è equivalente a *(v + i)

  • Calcolo dello spiazzamento nel vettore grazie all’aritmetica dei puntatori:
  • L’indirizzo della cella i-esima è: v + i, la dereferenziazione di tale valore, permette di accedere al contenuto della cella i-esima.

Esempio

Inizializzazione puntatori

Doppio puntatore → è una variabile puntatore che contiene l’indirizzo di un’altra variabile puntatore

int *pPtr = NULL;
int ** pDblptr = NULL; //doppio puntatore
int * vett[3] = {NULL, NULL, NULL}; //ogni cella del vettore è un puntatore a int
int x = 1, y = 5;

pPtr = &x;//Assegno l'indirizzo di x a pPtr
pDblptr = &pPtr;//Assegno a pDblptr, l'indirizzo di pPtr

vett[0] = vett[1] = vett[2] = pPtr; //Le celle del vattore puntano a pPtr(indirizzo di x), il quale punta a x
Rappresentazione codice puntatori
*pDblptr = &y; //Sovrascrivo l'indirizzo di y al contenuto di pDblptr che contiene pPtr
vett[1] = pPtr; //La seconda cella dell'array punterà a pPtr che ora punta a y
vett[2] = *pDblptr; //La terza cella dell'array punterà a pDblptr che punta a pPtr e quindi a y
Rappresentazione codice puntatori
**pDblptr = 6; //Prendi il valore 6 ed inseriscilo in y
**vett = 7; //equivale a *vett[0] = 7. Inserisco il valore 7 in x
*(vett[2]) = *vett[1] + 1 + *vett[0]; //y = 6 + 1 + 7 -> y = 14 
Rappresentazione codice puntatori
[adinserter block="7"]

Struct e Puntatori

typedef struct 
{
	int PrimoCampo;
	char SecondoCampo;
}TipoDato;

TipoDato t;
TipoDato *p = &t;

int *y;
y = &(t.PrimoCampo); //La posizione di memoria dell'attributo PrimoCampo, della variabile t

(*p).Primocampo = 12; //Assegnamo al campo PrimoCampo il valore 12, equivale a t.PrimoCampo = 12
p -> PrimoCampo = 12; //è equivalente alla scrittura di sopra

Il C assegna un indirizzo ad ogni cella di memoria che contiene 1Byte. L’indirizzo di una variabile rappresenta quindi l’indirizzo del primo byte della sequenza di celle utilizzata per rappresentare il valore.

L’operatore sizeof() dà il numero di byte occupati da un tipo o una variabile.

double d, A[5], *p =&d;
sizeof(double); //restituisce 8
sizeof(A); //8 * 5 = 40
sizeof(*p); //E' grande quanto un double, quindi 8
sizeof(A[2]); //E' un double, quindi restituisce 8
sizeof(p); //Restituisce 4 in un calcolatore a 32 bit Perchè è il numero di byte che ci servono per rappresentare un indirizzo in memoria

Aritmetica con puntatori

Il C permette somme e sottrazioni con i puntatori

p += 5; //p = p + 5

L’effetto è quello di avere il valore p incrementato di un multiplo dell’ingombro in memoria del tipo puntato:

p=p+5 → p = 5 * (sizeof(TipoPuntatoDa_p));

Anche per i vettori, visto che v[3] = 2 equivale a (v+3) = 2; Il calcolatore modifica il valore effettuando la comma come

*v + 3**sizeof(int)

  • Se p e q puntatno a due diversi elementi di uno stesso array
int v[5] = {14,16,8,9,12};
int *p = &v[3], *q = &v[1]; //p = 9, q = 16

La differenza p – q dà la distanza espressa in numero di celle dell’array tra gli elementi puntati. p – q = 2

int x[3] = {2,7,4}, *p = x; //Prende l'indirizzo di memoria del primo elemento del vettore p=&x[0]

(*p)++; //Mette il valore 3 nella prima cella dell'array (*p) = (*p)+1

*(p++); //p = p+1; (*p) -> E' uguale a 7, cioè il secondo elemento dell'array

Array multidimensionali

Per calcolare lo spiazzamento, l’esecutore deve conoscere le dimensioni intermedie.

int m[R][C]
*(*(m+i)+j);//equivale a *(m+C*i+j) che equivale a m[i][j]

//Array tridimensionale
int p[X][Y][Z];
*(*(*(p+i)+j)+k);//equivale a *(p+Y*Z*i + Z*j+k) che equivale a p[i][j][k]
[adinserter block="7"]

Premi qui per scaricare il PDF