Domande e risposte

Questa pagina contiene le risposte ad alcune domande che sono state poste da studenti.

  1. conversione da void * ad altri tipi
  2. errore sulla malloc
  3. non si riesce ad aprire il file
  4. passaggio di liste per indirizzo

Conversione da void * ad altri tipi

Alcuni studenti hanno ottenuto degli errori in istruzioni in cui è necessaria una conversione di una variabile di tipo void * a un puntatore di altro tipo, per esempio int *. Una situazione in cui questo errore si può verificare è la seguente:

#include

int main() {
  int *a;

  a=malloc(10*sizeof(int));

  return 0;
}

Se questo programma produce errore, vuol dire che una delle opzioni del compilatore non è corretta. In particolare, si può ottenere il seguente errore:

Cannot assign 'void *' to 'int *' in function main()

Questo errore è dovuto al fatto che si sta usando il compilatore C++ invece del compilatore C. Per evitare questo errore occorre:

  1. accertarsi di salvare il file con estensione .C piuttosto che .CPP (in altre parole, il file si deve chiamare per esempio TEST.C piuttosto che TEST.CPP);
  2. dire all'ambiente di usare il compilatore C invece di quello C++. Per fare questo: andare nel menu Opzioni, poi Compiler, e infine C++ options; appare una finestra in cui una delle opzioni è ``Use C++ compiler'': selezionare ``CPP extension only''.

Se il file è stato salvato con estensione .C, e le opzioni di compilazione sono corrette, l'errore non si dovrebbe più verificare.

Errore di conversione sulla malloc

Un errore di tipo Nonportable pointer conversion in una istruzione in cui il risultato della malloc viene assegnato a una variabile indica che si sta cercando di assegnare un puntatore a una variabile scalare o viceversa. Per esempio, la istruzione:

a=malloc(...);

Può generare questo errore in due casi:

  1. manca la inclusione #include<stdlib.h>
  2. la variabile a è stata definita come scalare e non come puntatore: per esempio, a è stata definita con int a; invece che con int *a.

Occorre quindi accertarsi che il file header sia stato incluso, e che la variabile a cui si assegna il risultato di malloc sia stata effettivamente dichiarata come una variabile puntatore. A questo proposito, si noti che la dichiarazione:

int *b, a;

Dichiara la variabile b come puntatore a intero, mentre la variabile a è un intero.

Non si riesce ad aprire il file

Molti programmi generano un fallimento quando si tenta di aprire un file in lettura. In particolare, si produce l'errore File not found, anche se il file con il nome specificato esiste.

Questo avviene perchè il programma generato dalla compilazione è in grado di leggere soltanto i file che si trovano nella directory corrente. Esistono due soluzioni:

  1. entrare nell'ambiente Dos (file->dos shell), copiare il file da leggere nella directory in cui si trova il programma eseguibile (quello che ha lo stesso nome del file sorgente C ma che ha estensione .exe), o viceversa, spostarsi nella directory che contiene entrambi i file, ed eseguire il programma.
  2. specificare il percorso completo del file: questo significa inserire non solo il suo nome, ma anche il nome completo della directory che lo contiene (a partire da C:\).

Attenzione: quando si specifica il nome completo di un file all'interno del programma C, come per esempio fd=fopen("C:\\tc\\examples\\albero.txt", occorre scrivere sempre due volte il simbolo backslash \. Questo non va fatto se invece il nome completo del file viene specificato come argomento, sia se il programma viene eseguito da dos, sia se l'argomento viene specificato nella finestra Arguments del menu Run.

Perchè si passa pl invece di &pl a una funzione che prende l'indirizzo di una lista?

La domanda è questa: ho una funzione, per esempio InserisciTestaLista, che riceve come parametro una lista, passata per indirizzo. Perchè in alcuni casi si passa InserisciTestaLista(&l, x) e in altri InserisciTestaLista(pl, x)?

Per esempio, se si vuole creare una lista con tre elementi 1, 2, 3 si fanno le tre chiamate seguenti:

TipoLista CreaLista(void) {
  TipoLista l;

  l=NULL;

  InserisciTestaLista(&l, 3);
  InserisciTestaLista(&l, 2);
  InserisciTestaLista(&l, 1);

  return l;
}

Nella seguente funzione, che inserisce un elemento e il suo doppio in una lista di interi, si usa una chiamata in cui si passa pl.

void InserisciDoppio(TipoLista *pl, int x) {
  InserisciTestaLista(pl, x);	/* inserisce x */
  InserisciTestaLista(pl, 2*x);	/* inserisce il doppio di x */
}

In realtà, in tutti e due i casi, la funzione InserisciTestaLista riceve l'indirizzo di una lista, per cui non c'è nessuna inconsistenza. In particolare, la funzione deve ricevere una variabile di tipo TipoLista *. Nel primo caso, l è di tipo TipoLista, per cui va passato il suo indirizzo (che si trova con l'operatore &). Nel secondo caso, la variabile pl è già una variabile di tipo puntatore a lista, ossia TipoLista *. Quindi è questa la variabile che va passata (e non il suo indirizzo, che sarebbe di tipo TipoLista **).

Se il perchè di questa differenza non è ancora chiaro, è probabile che si stia usando una regola sbagliata, la seguente:

Regola sbagliata: se una funzione riceve una variabile per indirizzo, questa va chiamata mettendo & davanti alla variabile.

Questa regola non è del tutto esatta: in particolare, in alcuni casi è corretta, in altri no. La regola vera è la seguente:

Regola esatta: il tipo del parametro formale e del parametro attuale devono essere lo stesso. Se una funzione prende come parametro l'indirizzo di una variabile di un tipo, essa va chiamata passando l'indirizzo di una variabile di quel tipo.

Applichiamo questa regola ai due casi di sopra. Nel caso della creazione di una lista di tre elementi, la variabile l è una variabile di tipo TipoLista, mentre la funzione InserisciTestaLista prende come parametro l'indirizzo di una lista, ossia l'indirizzo di una variabile di tipo TipoLista. Quindi, occorre passare ad essa l'indirizzo di l, e quindi si passa &l. Si noti che il fatto che le variabili di tipo TipoLista siano a loro volta dei puntatori è totalmente irrilevante: la regola vale comunque, sia sulle variabili scalari (es. interi) che sui puntatori. La seguente figura illustra lo stato della memoria dopo l'inserimento del numero 3.

Consideriamo ora il secondo caso, quello dell'inserimento di un elemento e del suo doppio in una lista. Come si è già detto la funzione InserisciTestaLista riceve l'indirizzo di una lista. La funzione InserisciDoppio, d'altra parte, riceve come parametro una variabile di tipo TipoLista *, ossia un puntatore a una lista. In altre parole, all'interno della funzione InserisciDoppio, la variabile pl è l'indirizzo di una variabile di tipo TipoLista. Dal momento che questo è quello che la funzione di inserimento in testa vuole, questo va passato. In altre parole: pl è l'indirizzo di una variabile lista, e la funzione InserisciTestaLista prende come parametro l'indirizzo di una lista. Quindi, si passa direttamente pl. Se si fosse passato &pl, questo sarebbe stato l'indirizzo di una variabile che contiene l'indirizzo di una lista, e questo non è quello che la funzione InserisciTestaLista prende come parametro. Per esempio, possiamo avere la seguente situazione, all'inizio della esecuzione, se alla funzione InserisciDoppio viene passata per indirizzo la lista (2 -9), ossia pl è l'indirizzo della lista (2 -9).

La tavola seguente riassume il concetto.

Funzione Nome della variabile Tipo della variabile InserisciTestaLista prende Cosa si passa
CreaLista l TipoLista indirizzo di una var TipoLista &l
InserisciDoppio pl TipoLista *
(indirizzo di una variabile TipoLista)
indirizzo di una var TipoLista pl

L'unico concetto è che va passato quello che la funzione richiede, ossia l'indirizzo di una lista. Nel primo caso abbiamo l, che è una lista, per cui va passato il suo indirizzo. Nel secondo caso abbiamo pl, che è l'indirizzo di una lista, e questo va passato.