Tipo dei puntatori

  1. perchè i puntatori non sono interi?
  2. perchè altrimenti non so in che modo *p va interpretato (è un intero? un carattere?)
  3. alternativa possibile ma non usata: * con due argomenti

Nelle pagine precedenti abbiamo visto come i puntatori siano effettivamente dei numeri. Si distinguono dai numeri interi solo per il fatto che il loro valore indica l'indirizzo di una locazione, ossia indica una posizione della memoria.

Ci si potrebbe a questo punto chiedere perchè

  1. i tipi puntatori sono di tipo diverso dagli interi?
  2. i puntatori non hanno tutti lo stesso tipo?

In effetti, il tipo di un puntatore è una cosa che può venire facilmente scavalcata: per esempio, un programma in cui x è un intero e p è un puntatore può contenere la istruzione x=p; oppure p=x;. Il compilatore produce un messaggio di avvertimento (warning), ma la compilazione ha successo. Lo stesso vale per la distinzione fra puntatori a due tipi diversi: se p è un puntatore a intero e t è un puntatore a reale, allora le istruzioni p=t e t=p sono ammesse, anche se si genera un warning in compilazione. Il programma tipipunt.c contiene alcuni esempi di assegnazioni fra tipi diversi.

/*
  Assegnazioni fra tipi sbagliati.
*/

int main() {
  int x;
  int *p;
  float *t;


  x=p;		/* assegnazione a variabile int * di valore int */

  t=x;		/* assegnazione a variabile float * di valore int */

  p=t;		/* assegnazione a variabile int * di valore float *
                */

  return 0;
}

Si noti anche che i messaggi di errore possono facilmente venire eliminati con il cast.

A questo punto, ci si chiede come mai i puntatori non sono semplicemente di tipo int. Il motivo è che in questo modo, non sarebbe chiaro quale è il tipo della espressione *p. Infatti, se p è semplicemente un intero, allora *p potrebbe essere a sua volta un intero, oppure un carattere, oppure un reale, ecc.

In altre parole, se i puntatori fossero interi, allora le istruzioni p=&c, p=&x e p=&f sarebbero tutte accettabili anche se p fosse intero, ma c, x e f sono un carattere, un intero e un reale. A questo punto non sarebbe più possibile scrivere semplicemente *p, perchè p potrebbe puntare a un carattere, a un intero, oppure a un reale. Scrivendo *p non sarebbe possibile capire quale è il tipo di questa espressione, e quindi non si capirebbe nemmeno quale è la zona di memoria a cui il puntatore si riferisce (se fosse un puntatore a carattere, allora sarebbe una zona grande 1, se fosse un puntatore a intero la zona sarebbe grande 4, ecc).

Proviamo a immaginare una variante del C in cui i puntatori siano semplicemente di tipo intero. Consideriamo il seguente codice.

/*
  Quello che segue non e' codice C corretto.
*/

int main() {
  int px;
  float g;

  px=925434;

  g=*px / 2;

  return 0;
}

La espressione *px / 2 dice che devo prendere l'oggetto puntato da px e dividere per due questo valore. Però non dice quale è il tipo di *px. Questa espressione potrebbe quindi venire interpretata in (almeno) tre modi:

  1. prendi il byte che sta all'inidirizzo scritto in px, e dividilo per due;
  2. prendi i quattro byte a partire dall'indirizzo che sta scritto in px, e interpreta questi quattro byte come un intero; dividilo per due usando la divisione fra interi;
  3. prendi gli stessi quattro byte, e interpretali come un numero float; dividi questo numero usando la divisione fra reali.

Il risultato sarebbe diverso: nel primo caso si prende il primo byte invece dei primi quattro; il secondo e il terzo caso sono diversi perchè numeri reali e numeri interi, anche se occupano lo stesso spazio, sono rappresentati in modo diverso (in più, il risultato è anche diverso perchè sono diverse le regole della divisione fra interi e fra reali).

Tutto questo serve a dire:

il puntatore p ha un tipo, altrimenti sarebbe impossibile capire il tipo di *p, e quindi capire quanto è grande e come va interpretata la zona di memoria puntata da p.

Una possibile alternativa alla soluzione dei tipi sui puntatori sarebbe stata quella di definire l'operatore di oggetto puntato * come un operatore binario, il cui primo argomento è il puntatore e il secondo è il tipo dell'oggetto puntato. In altre parole, i puntatori non avrebbero bisogno dei tipi se, per prendere l'oggetto puntato da una variabile p, dovessi fare *(p,int) se voglio un intero, *(p,float) se voglio un float, ecc. Non è però questa la soluzione seguita in C.