Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


I puntatori (Parte 1), Dispense di Fondamenti di informatica

Spiegazione dei puntatori nel linguaggio C

Tipologia: Dispense

2018/2019

Caricato il 08/03/2019

aaron.
aaron. 🇮🇹

8 documenti

1 / 39

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Dispense del corso di
Fondamenti di Informatica 1
Il linguaggio C - Puntatori 1
Aggiornamento del 17/10/2018
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27

Anteprima parziale del testo

Scarica I puntatori (Parte 1) e più Dispense in PDF di Fondamenti di informatica solo su Docsity!

Dispense del corso di

Fondamenti di Informatica 1

Il linguaggio C - Puntatori 1

Aggiornamento del 17/10/

I puntatori

  • Abbiamo visto come, nell’architettura ADE8, sia stato necessario

introdurre il concetto di accesso indiretto per consentire di lavorare con

più dati di cui era noto solo l’indirizzo iniziale.

  • Per fare questo è stato necessario utilizzare il valore contenuto in una

cella come indirizzo.

  • In C non sarebbe sufficiente un semplice tipo «indirizzo». Quello che

bisogna specificare è anche il tipo di dato di cui si gestisce l’indirizzo.

  • In C esiste quindi il puntatore ad un tipo di dato. Ovvero avremo

puntatori a char, puntatori ad unsigned char, puntatori a short, puntatori

ad unsigned short, puntatori a int, ...

  • Un puntatore quindi è il tipo di dato che rappresenta l’indirizzo di una

variabile di un tipo ben specificato.

  • La dimensione in memoria di un puntatore non dipende dal tipo di dato a

cui punta, dato che tutti gli indirizzi sono uguali e dipendono

dall’architettura.

  • A meno di non cambiare il default, su Visual Studio 2017 i puntatori sono

a 32 bit.

Utilizzo dei puntatori: operatore &

  • Supponiamo di avere una variabile x di tipo char e voler puntare a quella

variabile con il puntatore p. Come faccio ad assegnare a p l’indirizzo di

x? Mi serve un operatore che ritorni l’indirizzo di x:

&

  • L’operatore unario & restituisce l’indirizzo della variabile a cui è applicato.

Ad esempio possiamo fare:

int main(void)
char x = 3;
char *p;
p = &x;
return 0;
  • In questo modo p è una variabile di tipo puntatore che contiene l’indirizzo

di x. Si dice che «p punta a x».

Utilizzo dei puntatori: operatore *

  • Ok, ma a che cosa serve avere un puntatore? Come posso cioè

utilizzarlo?

  • Supponiamo ad esempio di voler leggere il valore della memoria

all’indirizzo puntato da p. Come fare? Bisogna utilizzare un operatore

che, dato il puntatore, restituisca la variabile puntata :

*

  • Ma come? Non abbiamo già visto che l’asterisco serve per definire una

variabile di tipo puntatore? Purtroppo sì, cioè in C l’asterisco, come visto

per le parentesi tonde, viene utilizzato in diversi modi e a seconda del

caso assume significati diversi:

  • in una espressione tipo a*b, è l’operatore binario di moltiplicazione;
  • in una espressione tipo a = *b, è l’operatore unario di dereferenziazione.

Ovvero, l’espressione *b è il valore della variabile all’indirizzo b.

  • In una definizione tipo int *a;, è il modificatore che definisce a come

puntatore a int.

Applicazione dei puntatori

  • Riprendiamo l’esempio sbagliato che abbiamo già visto in precedenza,

dove tentavamo di scrivere una funzione in grado di raddoppiare un

valore numerico:

int y = 7; void raddoppia (int x) { x = x * 2; } int main(void) { raddoppia(y); return 0; }

  • Abbiamo già detto che al momento della chiamata alla funzione

raddoppia, viene creato il parametro x della funzione inizializzato al

valore di y. Quindi modificare il valore di x non ha alcun effetto sulla cella

di memoria che chiamiamo y.

  • Possiamo «legare» le due variabili trasformando x in un puntatore.

 Programma errato di esempio

Applicazione dei puntatori

  • La versione funzionante della funzione è la seguente: int y = 7; void raddoppia (int *x) { *x = *x * 2; } int main(void) { raddoppia(&y); return 0; }
  • Non è proprio bella da leggere o da usare, ma funziona:
    • Invece che passare a raddoppia il valore di y, passo l’indirizzo;
    • Nella funzione dereferenzio (neologismo…) il parametro con l’operatore * e

quindi leggo e scrivo le celle di memoria in cui è contenuto y.

  • Al termine della funzione, la variabile x viene distrutta, ma tanto le modifiche

sono state applicate direttamente a y.

  • In generale, potendo, la soluzione che non utilizza puntatori è preferibile!

Applicazione dei puntatori

  • Ecco cosa potrebbe pensare di scrivere un programmatore distratto: void swap(unsigned int a, unsigned int b) { unsigned int tmp = a; a = b; b = tmp; } unsigned int MCD(unsigned int m, unsigned int n) { if (m == 0 || n == 0) return 0; while (m != n) { if (m < n) swap(m, n); m - = n; } return m; }
  • Ovviamente questa funzione non fa nulla!!! I valori di a e b vengono

scambiati, ma quelli di m e n rimarranno esattamente identici a prima.

  • Quello che si deve fare è utilizzare i puntatori per modificare le variabili m

e n.

 Programma errato di esempio

Applicazione dei puntatori

  • La versione corretta è la seguente: void swap(unsigned int *a, unsigned int *b) { unsigned int tmp = *a; *a = *b; *b = tmp; } unsigned int MCD(unsigned int m, unsigned int n) { if (m == 0 || n == 0) return 0; while (m != n) { if (m < n) swap(&m, &n); m - = n; } return m; }
  • Qui, passiamo alla funzione gli indirizzi delle variabili da scambiare e

tramite questi indirizzi, modifichiamo il contenuto della memoria di quelle

variabili.

Visualizzazione grafica dell’operazione di swap

  • Viene invocata la funzione swap e questo crea le nuove variabili a e b:

m 3

MCD

n 6

a?

swap

b?

void swap(unsigned int *a, unsigned int *b){

unsigned int tmp = *a;

*a = *b;

*b = tmp;

Visualizzazione grafica dell’operazione di swap

  • Viene invocata la funzione swap e questo crea le nuove variabili a e b:
  • Poi avviene l’inizializzazione di a e b con &m e &n:

m 3

MCD

n 6

a

swap

b

void swap(unsigned int *a, unsigned int *b){

unsigned int tmp = *a;

*a = *b;

*b = tmp;

Visualizzazione grafica dell’operazione di swap

  • Viene invocata la funzione swap e questo crea le nuove variabili a e b.
  • Poi avviene l’inizializzazione di a e b con &m e &n.
  • Si entra nel corpo della funzione e viene creata la variabile tmp.
  • tmp viene inizializzata con *a, ovvero il valore puntato da a (3):

m 3

MCD

n 6

a

swap

b

void swap(unsigned int *a, unsigned int *b){

unsigned int tmp = *a;

*a = *b;

*b = tmp;

tmp 3

Visualizzazione grafica dell’operazione di swap

  • Alla cella puntata da a si assegna il valore di quella puntata da b (6):

m 6

MCD

n 6

a

swap

b

void swap(unsigned int *a, unsigned int *b){

unsigned int tmp = *a;

*a = *b;

*b = tmp;

tmp 3

Visualizzazione grafica dell’operazione di swap

  • Alla cella puntata da a si assegna il valore di quella puntata da b (6).
  • Alla cella puntata da b si assegna il valore contenuto in tmp (3).
  • Al termine della funzione, le variabili e i parametri vengono eliminati e si

ritorna alla funzione chiamante:

m 6

MCD

n 3

swap

void swap(unsigned int *a, unsigned int *b){

unsigned int tmp = *a;

*a = *b;

*b = tmp;

Un possibile errore

  • Lavorando coi puntatori, è facile confondersi e cambiare il valore del

puntatore (cioè farlo puntare da un’altra parte) invece che il valore della

cella puntata:

void swap(unsigned int *a, unsigned int *b) { unsigned int *tmp = a; a = b; b = tmp; } unsigned int MCD(unsigned int m, unsigned int n) { if (m == 0 || n == 0) return 0; while (m != n) { if (m < n) swap(&m, &n); m - = n; } return m; }

  • Questa funzione sembra fare quanto richiesto, ma all’uscita, di nuovo,

nulla è cambiato.

 Programma errato di esempio